valent-pipeline 0.19.27 → 0.19.29
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
|
@@ -596,6 +596,19 @@ program
|
|
|
596
596
|
await checkSpecConformanceCmd(options);
|
|
597
597
|
});
|
|
598
598
|
|
|
599
|
+
// check-groom-currency — re-groom guard: is a GROOMED story's spec stale vs active correction-directives?
|
|
600
|
+
program
|
|
601
|
+
.command('check-groom-currency')
|
|
602
|
+
.description('Exit 1 if a GROOMED story\'s spec predates the latest correction-directives change (the sprint then re-specs instead of skip-spec on a stale groomed spec); exit 0 if current. Graceful no-op without git/directives/spec.')
|
|
603
|
+
.requiredOption('--story <id>', 'Story identifier')
|
|
604
|
+
.option('--root <dir>', 'Project root (defaults to the current directory)')
|
|
605
|
+
.option('--manifest <path>', 'Spec manifest path (defaults to stories/<id>/output/qa-test-spec.manifest.json)')
|
|
606
|
+
.option('--directives <path>', 'Correction-directives path (defaults to knowledge/correction-directives.yaml)')
|
|
607
|
+
.action(async (options) => {
|
|
608
|
+
const { checkGroomCurrency } = await import('../src/commands/check-groom-currency.js');
|
|
609
|
+
await checkGroomCurrency(options);
|
|
610
|
+
});
|
|
611
|
+
|
|
599
612
|
// spec commands — mechanical spec-phase gate (Phase 2: READINESS Step 0 dissolved into code)
|
|
600
613
|
const specCmd = program
|
|
601
614
|
.command('spec')
|
package/package.json
CHANGED
|
@@ -391,6 +391,20 @@ const SPECCHECK_SCHEMA = {
|
|
|
391
391
|
},
|
|
392
392
|
}
|
|
393
393
|
|
|
394
|
+
// GROOM-CURRENCY gate return (re-groom guard): is a groomed story's spec stale vs active directives?
|
|
395
|
+
const GROOM_CURRENCY_SCHEMA = {
|
|
396
|
+
type: 'object',
|
|
397
|
+
required: ['schema', 'story', 'stale'],
|
|
398
|
+
additionalProperties: true,
|
|
399
|
+
properties: {
|
|
400
|
+
schema: { const: 1 },
|
|
401
|
+
agent: { type: 'string' },
|
|
402
|
+
story: { type: 'string' },
|
|
403
|
+
stale: { type: 'boolean' },
|
|
404
|
+
reason: { type: 'string' },
|
|
405
|
+
},
|
|
406
|
+
}
|
|
407
|
+
|
|
394
408
|
// RED gate return (Phase 2 ATDD). `noAcceptance` = the CLI no-op'd (pre-ATDD story); `weakRed` =
|
|
395
409
|
// the suite could not run pre-implementation and weak_red=allow accepted it (carried to JUDGE);
|
|
396
410
|
// `prePassingCases` = acceptance cases that PASSED before implementation (spec bugs -> QA-A).
|
|
@@ -1975,6 +1989,18 @@ async function runStory(story) {
|
|
|
1975
1989
|
// source story's ACs still hold) from the bug's backlog fields and the source story's existing specs,
|
|
1976
1990
|
// so the normal dev agents have the inputs they read. Then Build -> Critic -> QA -> Judge run as usual.
|
|
1977
1991
|
// Spec/SpecCheck (REQS/UXA/QA-A + the mechanical gate) are skipped — a bug fixes existing code, it is not re-specced.
|
|
1992
|
+
|
|
1993
|
+
// Re-groom guard (2026-06-17): if a correction-directive landed SINCE this groomed story was specced
|
|
1994
|
+
// (e.g. CD-11-1's "infra-invariant ACs are required:false" rule), its on-disk spec predates the
|
|
1995
|
+
// directive and is stale — fall through to the full Spec path instead of the skip-spec-on-groomed
|
|
1996
|
+
// shortcut, so the new directive actually shapes the spec. Cheap deterministic check; fails open.
|
|
1997
|
+
let specStale = false
|
|
1998
|
+
if (groomed && itemType !== 'bug') {
|
|
1999
|
+
const currency = await runGroomCurrencyGate(storyId)
|
|
2000
|
+
specStale = currency.stale === true
|
|
2001
|
+
if (specStale) log(`${storyId}: groomed spec is STALE vs active directives (${currency.reason}) — re-speccing`)
|
|
2002
|
+
}
|
|
2003
|
+
|
|
1978
2004
|
if (itemType === 'bug') {
|
|
1979
2005
|
phase('Spec')
|
|
1980
2006
|
log(`${storyId}: bug-fix item — running bug-intake (minimal brief + regression spec), skipping full Spec + Readiness`)
|
|
@@ -1989,7 +2015,7 @@ async function runStory(story) {
|
|
|
1989
2015
|
`that context write a MINIMAL reqs-brief.md (the fix scope) and qa-test-spec.md (a regression proving the ` +
|
|
1990
2016
|
`defect is gone plus the affected ACs still pass) into this item's output dir. Do NOT re-spec the whole story.`,
|
|
1991
2017
|
{ label: `bug-intake:${storyId}`, phase: 'Spec', model: modelFor('REQS') })
|
|
1992
|
-
} else if (groomed) {
|
|
2018
|
+
} else if (groomed && !specStale) {
|
|
1993
2019
|
// Spec + Readiness are SKIPPED for pre-groomed stories. plan.workflow.js already specced them
|
|
1994
2020
|
// (reqs-brief/uxa-spec/qa-test-spec are on disk) and passed the spec gate at planning time,
|
|
1995
2021
|
// so re-running here is duplicated token spend. The standalone single-story path leaves `groomed`
|
|
@@ -2529,6 +2555,24 @@ async function runStory(story) {
|
|
|
2529
2555
|
// re-derivation: the spec is a derivation chain (REQS -> UXA -> QA-A); reworking upstream
|
|
2530
2556
|
// without re-deriving downstream guarantees the next cycle re-rejects on staleness. Cap-trip
|
|
2531
2557
|
// throws — a story whose spec artifacts cannot be completed must not reach Build.
|
|
2558
|
+
// Re-groom guard helper: run the deterministic groom-currency CLI (git commit-time of the spec vs
|
|
2559
|
+
// correction-directives.yaml) and return {stale, reason}. Fails open (stale:false) if it can't run,
|
|
2560
|
+
// so a missing/unavailable check never forces a re-spec we can't justify.
|
|
2561
|
+
async function runGroomCurrencyGate(sid) {
|
|
2562
|
+
const v = await agent(
|
|
2563
|
+
[
|
|
2564
|
+
`You are **GROOM-CURRENCY**, a mechanical check for story ${sid} in the valent-pipeline.`,
|
|
2565
|
+
`Run this ONE command from ${runDirPhrase(sid)}, capturing its exit code and last stdout line (JSON):`,
|
|
2566
|
+
` node .valent-pipeline/bin/cli.js check-groom-currency --story ${sid}`,
|
|
2567
|
+
`Exit 1 = the groomed spec is STALE vs active correction-directives; exit 0 = current. Transcribe, never interpret.`,
|
|
2568
|
+
`Return ONLY { schema:1, agent:"groom-currency", story:"${sid}", stale:boolean, reason:string } as JSON ` +
|
|
2569
|
+
`(stale=true iff the command exited 1; copy the command's JSON "reason").`,
|
|
2570
|
+
].join('\n'),
|
|
2571
|
+
{ label: `gate:groom-currency:${sid}`, phase: 'Spec', schema: GROOM_CURRENCY_SCHEMA, model: modelFor('SPECCHECK') },
|
|
2572
|
+
)
|
|
2573
|
+
return v && typeof v.stale === 'boolean' ? v : { stale: false, reason: 'currency check unavailable — treated as current' }
|
|
2574
|
+
}
|
|
2575
|
+
|
|
2532
2576
|
async function runSpecCheckGate(sid) {
|
|
2533
2577
|
// Full trail: spec artifacts land as `docs({id}): …` before the mechanical checks — and each
|
|
2534
2578
|
// spec-rework cycle re-commits (the commit is a no-op on an already-clean tree).
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { existsSync } from 'fs';
|
|
2
|
+
import { isAbsolute, join } from 'path';
|
|
3
|
+
import { execFileSync } from 'child_process';
|
|
4
|
+
|
|
5
|
+
function resolveRoot(options) {
|
|
6
|
+
const r = options.root || process.cwd();
|
|
7
|
+
return isAbsolute(r) ? r : join(process.cwd(), r);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Unix commit-time of the last commit touching `relPath`, or null if uncommitted / git unavailable. */
|
|
11
|
+
function lastCommitTime(root, relPath) {
|
|
12
|
+
try {
|
|
13
|
+
const out = execFileSync('git', ['log', '-1', '--format=%ct', '--', relPath], { cwd: root, encoding: 'utf-8' }).trim();
|
|
14
|
+
const t = parseInt(out, 10);
|
|
15
|
+
return Number.isFinite(t) ? t : null;
|
|
16
|
+
} catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* `valent-pipeline check-groom-currency --story <id> [--root <dir>] [--manifest <path>] [--directives <path>]`
|
|
23
|
+
*
|
|
24
|
+
* Is a GROOMED story's spec still CURRENT with the active correction-directives? The sprint skips the
|
|
25
|
+
* Spec phase for a story plan-time grooming already specced — a token win — but a directive that
|
|
26
|
+
* landed AFTER grooming (canonical: CD-11-1's "infra-invariant ACs are required:false" rule) never
|
|
27
|
+
* shaped that on-disk spec, so the skip silently reuses a STALE spec. (Story 2.1 hit exactly this: its
|
|
28
|
+
* spec, groomed before CD-11-1, marked infra invariants required:true and the ATDD red gate rejected
|
|
29
|
+
* it for premature-pass — a manual forced re-groom was the workaround.)
|
|
30
|
+
*
|
|
31
|
+
* STALE iff `correction-directives.yaml` was committed AFTER the story's spec manifest. Prints a JSON
|
|
32
|
+
* verdict and exits 1 when STALE (the sprint workflow then re-specs), 0 when FRESH. Graceful no-op
|
|
33
|
+
* (FRESH / exit 0) when there are no directives, no committed spec, or no git — never forces a
|
|
34
|
+
* re-groom on missing data, so legacy / non-git projects are unaffected.
|
|
35
|
+
*/
|
|
36
|
+
export async function checkGroomCurrency(options) {
|
|
37
|
+
const root = resolveRoot(options);
|
|
38
|
+
const story = options.story;
|
|
39
|
+
if (!story) {
|
|
40
|
+
console.error('check-groom-currency: --story is required.');
|
|
41
|
+
process.exit(2);
|
|
42
|
+
}
|
|
43
|
+
const specRel = options.manifest || join('stories', story, 'output', 'qa-test-spec.manifest.json');
|
|
44
|
+
const dirRel = options.directives || join('knowledge', 'correction-directives.yaml');
|
|
45
|
+
const specAbs = isAbsolute(specRel) ? specRel : join(root, specRel);
|
|
46
|
+
const dirAbs = isAbsolute(dirRel) ? dirRel : join(root, dirRel);
|
|
47
|
+
|
|
48
|
+
if (!existsSync(dirAbs) || !existsSync(specAbs)) {
|
|
49
|
+
console.log(JSON.stringify({ ok: true, stale: false, reason: 'no active directives or no spec manifest on disk — nothing to re-groom against' }));
|
|
50
|
+
process.exit(0);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const specCt = lastCommitTime(root, specRel);
|
|
54
|
+
const dirCt = lastCommitTime(root, dirRel);
|
|
55
|
+
// Fail SAFE on missing git times: treat as fresh (do not force a re-groom we cannot justify).
|
|
56
|
+
const stale = !!(specCt && dirCt && dirCt > specCt);
|
|
57
|
+
const reason = stale
|
|
58
|
+
? `correction-directives.yaml was committed (${dirCt}) AFTER the spec manifest (${specCt}) — a directive landed since grooming; re-spec so it applies`
|
|
59
|
+
: (specCt && dirCt
|
|
60
|
+
? `spec manifest is at or after the latest directive change (spec ${specCt} >= directives ${dirCt}) — current`
|
|
61
|
+
: 'spec or directives uncommitted — treating as current (no re-groom forced on missing git data)');
|
|
62
|
+
console.log(JSON.stringify({ ok: true, stale, reason, specCommit: specCt, directivesCommit: dirCt }));
|
|
63
|
+
process.exit(stale ? 1 : 0);
|
|
64
|
+
}
|
package/src/lib/evidence.js
CHANGED
|
Binary file
|