valent-pipeline 0.19.38 → 0.19.40
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 +19 -5
- package/pipeline/orchestrators/claude-code/sprint.workflow.js +19 -0
- package/skills/valent-run-epic-workflow/SKILL.md +1 -1
- package/skills/valent-run-project-workflow/SKILL.md +1 -1
- package/src/commands/init.js +4 -0
- package/src/commands/upgrade.js +1 -1
- package/src/lib/config-schema.js +8 -0
package/package.json
CHANGED
|
@@ -30,9 +30,11 @@
|
|
|
30
30
|
* The return value is shaped to feed straight into sprint.workflow.js:
|
|
31
31
|
* { sprintId, points_planned, stories: [{ storyId, projectType, profiles }] }
|
|
32
32
|
*
|
|
33
|
-
* args: { stories: [{ storyId, projectType }], sprintId, velocity, lookaheadFactor?, groomBatchSize?, backlogPath?, maxRejectionCycles?, models?, packGate? }
|
|
33
|
+
* args: { stories: [{ storyId, projectType }], sprintId, velocity, velocityGrowthFactor?, lookaheadFactor?, groomBatchSize?, backlogPath?, maxRejectionCycles?, models?, packGate? }
|
|
34
34
|
* `velocity` is the current (seeded-SMA) velocity computed by the invoking skill; grooming runs
|
|
35
|
-
* ahead to `lookaheadFactor × velocity` (default 2) in chunks of `groomBatchSize` (default 10).
|
|
35
|
+
* ahead to `lookaheadFactor × velocity` (default 2) in chunks of `groomBatchSize` (default 10). The
|
|
36
|
+
* sprint PACKS at `velocity × velocityGrowthFactor` (default 1.1) — a deliberate stretch that pushes
|
|
37
|
+
* throughput up over time, governed by the per-story quality gates (see the packVelocity note below).
|
|
36
38
|
* `models` is the pipeline-config.yaml `models` tier->roles map, passed through by the invoking
|
|
37
39
|
* skill so per-agent model tiers stay config-driven (editable via `valent configure`). Omit it to
|
|
38
40
|
* use the baked-in default. See sprint.workflow.js for the full rationale.
|
|
@@ -199,6 +201,18 @@ const velocity = a.velocity
|
|
|
199
201
|
// workflow self-hosts when the args are absent.
|
|
200
202
|
const lookaheadFactor = typeof a.lookaheadFactor === 'number' && a.lookaheadFactor > 0 ? a.lookaheadFactor : 2
|
|
201
203
|
const groomBatchSize = Number.isInteger(a.groomBatchSize) && a.groomBatchSize > 0 ? a.groomBatchSize : 10
|
|
204
|
+
// Velocity STRETCH (2026-06-19): the planner PACKS at `velocity × velocityGrowthFactor`, not the raw
|
|
205
|
+
// trailing SMA. The SMA alone is a lagging, mean-reverting measure with a DOWNWARD ratchet — the packer
|
|
206
|
+
// caps points_shipped at velocity, so any reject/rollover/bug-only sprint pulls the SMA down and nothing
|
|
207
|
+
// ever pushes it back up (velocity can only decay). A flat growth factor applies constant upward pressure
|
|
208
|
+
// while the rigorous per-story gates stay the GOVERNOR: over-reach REJECTS → points_shipped drops → the
|
|
209
|
+
// SMA self-corrects, so throughput climbs only as fast as quality allows and settles at the
|
|
210
|
+
// quality-bounded maximum instead of decaying. Default 1.1 (+10%/sprint); config `sprint.velocity_growth_factor`,
|
|
211
|
+
// passed by the skill. Applied ONLY to the pack — grooming stays on the raw velocity (lookaheadFactor×raw
|
|
212
|
+
// = 2× already covers the 1.1× pack) and the retro's measured `velocity.sma` is left honest (measurement
|
|
213
|
+
// is not the target). round() makes the capacity a whole number (story points are integers).
|
|
214
|
+
const velocityGrowthFactor = typeof a.velocityGrowthFactor === 'number' && a.velocityGrowthFactor > 0 ? a.velocityGrowthFactor : 1.1
|
|
215
|
+
const packVelocity = Math.round(velocity * velocityGrowthFactor)
|
|
202
216
|
const backlogPath = a.backlogPath || 'pipeline-backlog.yaml'
|
|
203
217
|
const maxRejectionCycles = a.maxRejectionCycles ?? 3
|
|
204
218
|
// Pack-time freshness gate config (PUR-001 R-6.2), passed by the skill from pipeline-config.yaml
|
|
@@ -648,13 +662,13 @@ if (packGateCommands.length) {
|
|
|
648
662
|
phase('Pack')
|
|
649
663
|
// Deterministic greedy packing happens in code (src/lib/sprint.js), invoked via the CLI.
|
|
650
664
|
const pack = await agent(
|
|
651
|
-
`Run exactly: \`node .valent-pipeline/bin/cli.js sprint-pack --velocity ${
|
|
665
|
+
`Run exactly: \`node .valent-pipeline/bin/cli.js sprint-pack --velocity ${packVelocity} --backlog ${backlogPath}\` ` +
|
|
652
666
|
`in the project root and return its stdout JSON verbatim (fields: sprint_stories, buffer_story_ids, points_planned, remaining_capacity, over_budget).`,
|
|
653
667
|
{ label: 'sprint-pack', phase: 'Pack', schema: PACK_SCHEMA, model: modelFor('PACK') },
|
|
654
668
|
)
|
|
655
|
-
log(`packed ${pack.sprint_stories.length} stories (${pack.points_planned} pts); buffer: ${pack.buffer_story_ids.length}`)
|
|
669
|
+
log(`packed ${pack.sprint_stories.length} stories (${pack.points_planned} pts) at capacity ${packVelocity} (velocity ${velocity} × ${velocityGrowthFactor} stretch); buffer: ${pack.buffer_story_ids.length}`)
|
|
656
670
|
if (pack.over_budget) {
|
|
657
|
-
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 ${
|
|
671
|
+
log(`⚠ sprint ${sprintId} is OVER BUDGET: the highest-priority story exceeds the packing capacity ${packVelocity} (velocity ${velocity} × ${velocityGrowthFactor}) and was planned alone — consider splitting it (${pack.points_planned} pts vs ${packVelocity} capacity)`)
|
|
658
672
|
}
|
|
659
673
|
|
|
660
674
|
// Shaped to feed straight into sprint.workflow.js. Order by `pack.sprint_stories`, NOT by
|
|
@@ -1672,6 +1672,25 @@ async function runShipStep(sid, commandStr, stepPhase) {
|
|
|
1672
1672
|
log(`${sid}: ship-story attempt ${attempt}/${shipMergeAttempts} did not land (${res ? 'transient git-runner failure' : 'GIT runner died'}) — retrying the deterministic, idempotent merge`)
|
|
1673
1673
|
}
|
|
1674
1674
|
}
|
|
1675
|
+
// Deterministic post-ship verify (M-36/M-37, 2026-06-19): ship-story is a code-owned git merge, but
|
|
1676
|
+
// its result is transcribed by a haiku GIT runner — a transcription slip (or an agent death AFTER the
|
|
1677
|
+
// merge landed) drops `merged:true`, so the loop above reports "did not land" on a merge that ACTUALLY
|
|
1678
|
+
// SUCCEEDED, falsely blocking a shipped story (the git-ship-failed false-positive that stranded 2.20).
|
|
1679
|
+
// Git is the ground truth: when we did NOT confirm a land/conflict/skip, ask `git story-status` — its
|
|
1680
|
+
// `merged` is `isMergedInto` (computed in code, not by the agent); if the branch IS now an ancestor of
|
|
1681
|
+
// the target, the merge LANDED regardless of what the ship runner said → recover to merged:true. A real
|
|
1682
|
+
// conflict (broken out above) and a genuine non-landing (story-status merged:false → real merge-block)
|
|
1683
|
+
// are untouched. The ancestor check cleanly separates the two classes — field-validated by hand on 2.20
|
|
1684
|
+
// (M-38). Only runs on the rare non-landed path, so the happy ship adds zero overhead.
|
|
1685
|
+
const confirmed = !!(res && (res.merged === true || res.skipped === true || res.conflict === true))
|
|
1686
|
+
if (gitFlowEnabled && !confirmed) {
|
|
1687
|
+
const status = await runGitStep(sid,
|
|
1688
|
+
[`node .valent-pipeline/bin/cli.js git story-status --story ${sid}${gitFlags}`], 'merge-verify', stepPhase)
|
|
1689
|
+
if (status && status.merged === true) {
|
|
1690
|
+
log(`${sid}: ship-story runner reported no-land but git story-status confirms the branch IS merged into the target — recovering to merged:true (transcription false-failure, NOT a real merge-block)`)
|
|
1691
|
+
return { ...(res || {}), merged: true, recoveredByVerify: true }
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1675
1694
|
return res
|
|
1676
1695
|
}
|
|
1677
1696
|
|
|
@@ -149,7 +149,7 @@ Workflow({
|
|
|
149
149
|
...resolved.groomedBuffer,
|
|
150
150
|
],
|
|
151
151
|
sprintId: '{epic_id}-sprint-{n}', velocity: {computed seeded-SMA velocity, see above},
|
|
152
|
-
lookaheadFactor: {sprint.groom_lookahead_factor or 2}, groomBatchSize: {sprint.max_groom_batch_size or 10},
|
|
152
|
+
lookaheadFactor: {sprint.groom_lookahead_factor or 2}, groomBatchSize: {sprint.max_groom_batch_size or 10}, velocityGrowthFactor: {sprint.velocity_growth_factor or 1.1},
|
|
153
153
|
maxRejectionCycles: {quality.max_rejection_cycles or 3}, backlogPath: '{backlog_path}',
|
|
154
154
|
knowledge: { mode: <config.knowledge.mode or 'sqlite'>, curatedPath: <config.knowledge.curated_files_path or 'knowledge/curated'> },
|
|
155
155
|
models: <config.models or omit>, reasoning: <config.reasoning or omit>, packGate: <config.pack_gate or omit>,
|
|
@@ -149,7 +149,7 @@ Workflow({
|
|
|
149
149
|
...resolved.groomedBuffer,
|
|
150
150
|
],
|
|
151
151
|
sprintId: 'project-sprint-{n}', velocity: {computed seeded-SMA velocity, see above},
|
|
152
|
-
lookaheadFactor: {sprint.groom_lookahead_factor or 2}, groomBatchSize: {sprint.max_groom_batch_size or 10},
|
|
152
|
+
lookaheadFactor: {sprint.groom_lookahead_factor or 2}, groomBatchSize: {sprint.max_groom_batch_size or 10}, velocityGrowthFactor: {sprint.velocity_growth_factor or 1.1},
|
|
153
153
|
maxRejectionCycles: {quality.max_rejection_cycles or 3}, backlogPath: '{backlog_path}',
|
|
154
154
|
knowledge: { mode: <config.knowledge.mode or 'sqlite'>, curatedPath: <config.knowledge.curated_files_path or 'knowledge/curated'> },
|
|
155
155
|
models: <config.models or omit>, reasoning: <config.reasoning or omit>, packGate: <config.pack_gate or omit>,
|
package/src/commands/init.js
CHANGED
|
@@ -575,6 +575,10 @@ sprint:
|
|
|
575
575
|
groom_lookahead_factor: ${config.sprint?.groom_lookahead_factor ?? 2}
|
|
576
576
|
# Trailing window (sprints) for the velocity simple moving average, seeded with initial_velocity_points.
|
|
577
577
|
velocity_sma_window: ${config.sprint?.velocity_sma_window ?? 3}
|
|
578
|
+
# Velocity STRETCH: the planner packs at velocity_sma × this factor (not the raw SMA), so throughput
|
|
579
|
+
# climbs over time instead of decaying; the per-story quality gates govern it (over-reach rejects →
|
|
580
|
+
# the SMA self-corrects). 1.0 disables the stretch.
|
|
581
|
+
velocity_growth_factor: ${config.sprint?.velocity_growth_factor ?? 1.1}
|
|
578
582
|
|
|
579
583
|
orchestration:
|
|
580
584
|
recommended_context_window: "${config.orchestration?.recommended_context_window || '200k'}"
|
package/src/commands/upgrade.js
CHANGED
|
@@ -135,7 +135,7 @@ export async function upgrade(options = {}) {
|
|
|
135
135
|
let configContent = readFile(configPath);
|
|
136
136
|
if (!configContent.includes('sprint:')) {
|
|
137
137
|
// Insert sprint section before orchestration (or at end if orchestration not found)
|
|
138
|
-
const sprintBlock = `\nsprint:\n duration_minutes: 480\n initial_velocity_points: 60\n estimation_model: "calibrated"\n auto_plan: true\n fibonacci_scale: [1, 2, 3, 5, 8, 13, 21]\n max_groom_batch_size: 10\n groom_lookahead_factor: 2\n velocity_sma_window: 3\n`;
|
|
138
|
+
const sprintBlock = `\nsprint:\n duration_minutes: 480\n initial_velocity_points: 60\n estimation_model: "calibrated"\n auto_plan: true\n fibonacci_scale: [1, 2, 3, 5, 8, 13, 21]\n max_groom_batch_size: 10\n groom_lookahead_factor: 2\n velocity_sma_window: 3\n velocity_growth_factor: 1.1\n`;
|
|
139
139
|
const insertPoint = configContent.indexOf('\norchestration:');
|
|
140
140
|
if (insertPoint !== -1) {
|
|
141
141
|
configContent = configContent.slice(0, insertPoint) + sprintBlock + configContent.slice(insertPoint);
|
package/src/lib/config-schema.js
CHANGED
|
@@ -288,6 +288,7 @@ export function validateConfig(config) {
|
|
|
288
288
|
max_groom_batch_size: 'number',
|
|
289
289
|
groom_lookahead_factor: 'number',
|
|
290
290
|
velocity_sma_window: 'number',
|
|
291
|
+
velocity_growth_factor: 'number',
|
|
291
292
|
};
|
|
292
293
|
for (const [field, type] of Object.entries(sprintFields)) {
|
|
293
294
|
if (config.sprint[field] !== undefined && typeof config.sprint[field] !== type) {
|
|
@@ -562,6 +563,13 @@ export const defaults = {
|
|
|
562
563
|
// Trailing window (in sprints) for the velocity simple moving average. The SMA is seeded with
|
|
563
564
|
// initial_velocity_points so sprints 1..velocity_sma_window ramp smoothly before the seed ages out.
|
|
564
565
|
velocity_sma_window: 3,
|
|
566
|
+
// Velocity STRETCH multiplier. The planner PACKS at `velocity_sma × velocity_growth_factor` (not the
|
|
567
|
+
// raw SMA), applying constant upward pressure so throughput climbs over time instead of decaying — a
|
|
568
|
+
// plain SMA is lagging + mean-reverting and only ratchets DOWN (the packer caps shipped points at
|
|
569
|
+
// velocity, so any reject/rollover pulls it down and nothing pushes it back up). The per-story quality
|
|
570
|
+
// gates are the governor: over-reach rejects → shipped points drop → the SMA self-corrects, so velocity
|
|
571
|
+
// rises only as fast as quality holds and settles at the quality-bounded max. 1.0 disables the stretch.
|
|
572
|
+
velocity_growth_factor: 1.1,
|
|
565
573
|
},
|
|
566
574
|
orchestration: {
|
|
567
575
|
recommended_context_window: '200k',
|