@ikunin/sprintpilot 2.2.23 → 2.2.25

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.
@@ -1461,22 +1461,21 @@ function cmdStart(opts) {
1461
1461
  );
1462
1462
  }
1463
1463
  if (profile.lint_enabled) {
1464
- // Same honesty pattern as parallel_stories. Config is parsed; no
1465
- // state-machine integration yet. Users on lint_enabled=true would
1466
- // otherwise assume lint runs post-DEV_GREEN it doesn't.
1464
+ // v2.2.24: lint_enabled wires verifyDevGreen → post-green-gates.js
1465
+ // (lint-changed + lint-test-pitfalls + ci-parity scan). lint_blocking
1466
+ // governs whether a failed gate halts the autopilot or passes
1467
+ // through with a warning. The v2.2.23 "not wired" warning is gone —
1468
+ // lint runs for real now.
1467
1469
  ledger.append(
1468
1470
  {
1469
1471
  kind: 'state_transition',
1470
1472
  detail: {
1471
- lint_experimental_warning:
1472
- 'git.lint.enabled=true: config is read into the typed Profile but the state machine has no LINT_CHECK phase yet. Lint enforcement (per-language linters, blocking/non-blocking gate, output_limit) is tracked for v2.3.0+. Until then, bake lint into your test command (bmad-dev-story GREEN phase will catch failures).',
1473
+ lint_enabled: true,
1474
+ lint_blocking: !!profile.lint_blocking,
1473
1475
  },
1474
1476
  },
1475
1477
  { projectRoot },
1476
1478
  );
1477
- process.stderr.write(
1478
- '[autopilot] WARN git.lint.enabled=true but no LINT_CHECK phase exists yet (planned for v2.3.0). Bake lint into your test command for now.\n',
1479
- );
1480
1479
  }
1481
1480
 
1482
1481
  // Worktree health check — once per session, after lock acquire so we
@@ -1647,7 +1646,7 @@ function cmdRecord(opts) {
1647
1646
  profile.enabled === false && stateMachine.shouldSkipVerifyWhenGitDisabled(runtime.phase);
1648
1647
  let verifyResult;
1649
1648
  if (signal.status === 'success' && !isGitDisabledPhase) {
1650
- verifyResult = verifyMod.verify(runtime, signal.output, { projectRoot });
1649
+ verifyResult = verifyMod.verify(runtime, signal.output, { projectRoot, profile });
1651
1650
  ledger.append(
1652
1651
  { kind: 'verify_result', phase: runtime.phase, ok: verifyResult.ok, issues: verifyResult.issues || [] },
1653
1652
  { projectRoot },
@@ -1656,7 +1655,7 @@ function cmdRecord(opts) {
1656
1655
  verifyResult = verifyMod.verifyWithOverride(
1657
1656
  runtime,
1658
1657
  signal.output || {},
1659
- { projectRoot },
1658
+ { projectRoot, profile },
1660
1659
  signal.evidence || {},
1661
1660
  );
1662
1661
  ledger.append(
@@ -164,6 +164,54 @@ function autoDetectTestFiles(ctx, baseBranch) {
164
164
  return out;
165
165
  }
166
166
 
167
+ // Invoke scripts/post-green-gates.js when profile.lint_enabled is true.
168
+ // Returns null when:
169
+ // - profile.lint_enabled is false / absent (feature opt-out)
170
+ // - the script is missing (partial install)
171
+ // - projectRoot is unset
172
+ // Returns { failed: bool, summary?: string } on a real run.
173
+ //
174
+ // The script's contract: exit 0 = all gates pass, exit !=0 = at least
175
+ // one gate failed. JSON report on stdout when invoked with --json (we
176
+ // pass that flag). Failure summary captured for the issue message.
177
+ function runPostGreenGates(ctx) {
178
+ if (!ctx || !ctx.profile || !ctx.profile.lint_enabled) return null;
179
+ if (!ctx.projectRoot) return null;
180
+ const scriptRel = nodePath.join('_Sprintpilot', 'scripts', 'post-green-gates.js');
181
+ const scriptAbs = nodePath.join(ctx.projectRoot, scriptRel);
182
+ let fs;
183
+ try {
184
+ fs = ctx.fs || nodeFs;
185
+ if (!fs.existsSync(scriptAbs)) return null;
186
+ } catch {
187
+ return null;
188
+ }
189
+ const cp = require('node:child_process');
190
+ try {
191
+ const r = cp.spawnSync(
192
+ 'node',
193
+ [scriptAbs, '--json', '--project-root', ctx.projectRoot],
194
+ { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 120_000 },
195
+ );
196
+ if (r.status === 0) return { failed: false };
197
+ // Try to extract a brief summary from the JSON output. Fall back
198
+ // to the exit code if parsing fails.
199
+ let summary = `exit ${r.status}`;
200
+ try {
201
+ const parsed = JSON.parse(r.stdout || '{}');
202
+ if (parsed && parsed.failed_gate) summary = `failed_gate=${parsed.failed_gate}`;
203
+ if (parsed && parsed.first_issue) summary += `: ${parsed.first_issue}`;
204
+ } catch {
205
+ /* keep exit-code summary */
206
+ }
207
+ return { failed: true, summary };
208
+ } catch (_e) {
209
+ // Script crashed (e.g. ENOENT for node). Treat as non-failing —
210
+ // the lint phase should not gate the autopilot on its own bugs.
211
+ return null;
212
+ }
213
+ }
214
+
167
215
  // Probe the underlying git state to confirm a STORY_DONE signal whose
168
216
  // `git_steps_completed` flag was omitted. Returns true iff:
169
217
  // - commit_sha resolves locally (git cat-file -e <sha>)
@@ -227,6 +275,7 @@ function verify(state, signalOutput, context) {
227
275
  runner: (context && context.runner) || null,
228
276
  projectRoot: (context && context.projectRoot) || '.',
229
277
  augmented: (context && context.augmented) || null,
278
+ profile: (context && context.profile) || null,
230
279
  };
231
280
  const out = signalOutput || {};
232
281
  // Effective state: fall forward to signal.output for identity fields
@@ -275,15 +324,27 @@ function verifyCreateStory(state, _out, ctx) {
275
324
  const override = ctx.augmented || {};
276
325
  const ackMissingFm = override && override.acknowledge_missing_front_matter === true;
277
326
  if (!fm && !ackMissingFm) issues.push('story file missing YAML front-matter');
278
- // AC presence — look for "## Acceptance Criteria" section with at least one bullet.
279
- if (text && !/##\s+Acceptance Criteria[\s\S]*?\n-\s+/.test(text)) {
327
+ // AC presence — look for an Acceptance Criteria section with at
328
+ // least one list entry. Accepts:
329
+ // - heading levels ##, ###, #### (BMad standard is ##; some templates
330
+ // nest AC under Dev Notes which would use ###)
331
+ // - "Acceptance Criteria" / "Acceptance criteria" / "AC" (the abbr
332
+ // appears in some templates)
333
+ // - bullet markers `-` or `*` or numbered `1.` / `1)` lists
334
+ if (
335
+ text &&
336
+ !/#{2,4}\s+(?:Acceptance Criteria|Acceptance criteria|AC)\b[\s\S]*?\n[ \t]*(?:[-*]|\d+[.)])\s+\S/i.test(
337
+ text,
338
+ )
339
+ ) {
280
340
  issues.push('Acceptance Criteria section missing or empty');
281
341
  }
282
342
  // Tasks/Subtasks section with at least one task checkbox — required by
283
343
  // BMad bookkeeping. `bmad-create-story` produces unchecked `[ ]`
284
344
  // entries; `bmad-dev-story` flips them to `[x]`. If neither is present,
285
- // dev-story will have nothing to check off.
286
- if (text && !/##\s+Tasks(?:\s*\/\s*Subtasks)?[\s\S]*?(?:\[ \]|\[x\])/i.test(text)) {
345
+ // dev-story will have nothing to check off. Accept heading levels
346
+ // ## / ### / #### (templates sometimes nest Tasks under Dev Notes).
347
+ if (text && !/#{2,4}\s+Tasks(?:\s*\/\s*Subtasks)?[\s\S]*?(?:\[ \]|\[x\])/i.test(text)) {
287
348
  issues.push(
288
349
  'Tasks (or Tasks/Subtasks) section with at least one `[ ]` or `[x]` checkbox missing',
289
350
  );
@@ -381,6 +442,20 @@ function verifyDevGreen(state, out, ctx) {
381
442
  issues.push('tests_run must be a positive number (per AGENTS.md test-result format)');
382
443
  }
383
444
  }
445
+ // Post-GREEN gates: lint-changed + lint-test-pitfalls + ci-parity scan.
446
+ // Composed pipeline lives in scripts/post-green-gates.js. Only fires
447
+ // when profile.lint_enabled === true. Blocking vs non-blocking
448
+ // governed by profile.lint_blocking. Pre-2.2.24 the script existed
449
+ // and was documented as "called by the orchestrator after GREEN
450
+ // verify" but nothing actually invoked it.
451
+ const lintResult = runPostGreenGates(ctx);
452
+ if (lintResult) {
453
+ if (lintResult.failed && (ctx.profile && ctx.profile.lint_blocking)) {
454
+ issues.push(
455
+ `post-green-gates failed (lint_blocking=true): ${lintResult.summary || 'see ledger detail'}`,
456
+ );
457
+ }
458
+ }
384
459
  return { ok: issues.length === 0, issues };
385
460
  }
386
461
 
@@ -1,6 +1,6 @@
1
1
  addon:
2
2
  name: sprintpilot
3
- version: 2.2.23
3
+ version: 2.2.25
4
4
  description: Sprintpilot — autopilot and multi-agent addon for BMad Method (git workflow, parallel agents, autonomous story execution)
5
5
  bmad_compatibility: ">=6.2.0"
6
6
  modules:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ikunin/sprintpilot",
3
- "version": "2.2.23",
3
+ "version": "2.2.25",
4
4
  "description": "Sprintpilot — autopilot and multi-agent addon for BMad Method v6: git workflow, parallel agents, autonomous story execution",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {