valent-pipeline 0.19.37 → 0.19.39

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/bin/cli.js CHANGED
@@ -472,7 +472,7 @@ gitCmd
472
472
  .command('commit-phase')
473
473
  .description('Commit everything dirty as one typed conventional commit `{type}({story}): {summary}` with Story/Phase trailers; no-op when clean')
474
474
  .requiredOption('--story <id>', 'Story identifier')
475
- .requiredOption('--phase <phase>', 'Commit phase: spec | red | build | rework | review | evidence | ship | rollover | snapshot')
475
+ .requiredOption('--phase <phase>', 'Commit phase: spec | red | build | rework | review | evidence | ship | rollover | snapshot | sync | backlog')
476
476
  .option('--gate <gate>', 'Gate name for rework commits (static | critic | green | evidence)')
477
477
  .option('-m, --message <summary>', 'Commit summary (defaults per phase)')
478
478
  .option('--root <dir>', 'Project root (defaults to cwd)')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "valent-pipeline",
3
- "version": "0.19.37",
3
+ "version": "0.19.39",
4
4
  "description": "v3 multi-agent AI pipeline for software development lifecycle",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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 ${velocity} --backlog ${backlogPath}\` ` +
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 ${velocity} velocity)`)
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
@@ -1462,6 +1462,18 @@ const backlog = await persistBacklog(
1462
1462
  integration,
1463
1463
  )
1464
1464
 
1465
+ // (B) Commit the backlog dispositions persistBacklog just wrote (M-28/M-19 root). persistBacklog
1466
+ // runs `backlog ship|reject|requeue` (which WRITE pipeline-backlog.yaml) but nothing committed the
1467
+ // result — it rode the NEXT start-story snapshot, and a plan/checkout that ran first (the following
1468
+ // sprint) discarded the uncommitted write, silently reverting shipped stories to `groomed` (the
1469
+ // 2.9/2.10 status-drift). Commit it NOW, on the target, so no later checkout can drop it. commit-phase
1470
+ // is a no-op when the tree is clean (e.g. an all-skip resume where persist wrote nothing).
1471
+ if (gitFlowEnabled && !(backlog && backlog.skipped)) {
1472
+ await runGitStep('sprint', [
1473
+ `node .valent-pipeline/bin/cli.js git commit-phase --story sprint --phase backlog`,
1474
+ ], 'persist-commit', 'Backlog')
1475
+ }
1476
+
1465
1477
  return {
1466
1478
  shipped: results.every((r) => r.shipped),
1467
1479
  stories_shipped: shippedCount,
@@ -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>,
@@ -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'}"
@@ -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);
@@ -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',
@@ -53,6 +53,7 @@ export const PHASE_TYPES = {
53
53
  ship: 'chore', // final sweep before merge: post-review rework + QA/judge artifacts
54
54
  rollover: 'chore', // reject/blocked sweep so the branch holds everything for the fix/retry
55
55
  sync: 'chore', // re-ship prep: target merged into the retained branch (sync-story)
56
+ backlog: 'chore', // sprint-end backlog dispositions, committed AT persist so a later plan/checkout cannot discard the uncommitted write (M-28/M-19)
56
57
  };
57
58
 
58
59
  const DEFAULT_SUMMARIES = {
@@ -66,6 +67,7 @@ const DEFAULT_SUMMARIES = {
66
67
  rollover: 'roll over story work',
67
68
  snapshot: 'snapshot uncommitted work before story branch',
68
69
  sync: 'sync target into story branch before re-ship',
70
+ backlog: 'persist sprint backlog dispositions',
69
71
  };
70
72
 
71
73
  function git(cwd, args, opts = {}) {