valent-pipeline 0.19.24 → 0.19.26

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "valent-pipeline",
3
- "version": "0.19.24",
3
+ "version": "0.19.26",
4
4
  "description": "v3 multi-agent AI pipeline for software development lifecycle",
5
5
  "type": "module",
6
6
  "bin": {
@@ -39,7 +39,7 @@
39
39
  "items": { "type": "string" },
40
40
  "description": "Distinctive tokens that MUST appear verbatim in the implementing test code (Step 9b)."
41
41
  },
42
- "required": { "type": "boolean", "description": "Defaults true; false = gate warns instead of failing, and a skip of this case is auto-waived." },
42
+ "required": { "type": "boolean", "description": "Defaults true (behavioral case: must FAIL at the red gate, PASS at green). false = exempt from the red-required set (gate warns instead of failing) and skip auto-waived — use for a regression-guard asserting an already-satisfied infrastructure invariant the story must not regress (already green at red-time; would pass on an empty checkout). See steps/qa-a/write-acceptance-tests.md; CRITIC's acceptance audit flags behavioral cases mislabeled required:false." },
43
43
  "ac": {
44
44
  "type": "array",
45
45
  "minItems": 1,
@@ -21,6 +21,13 @@ For EACH test case in qa-test-spec.md:
21
21
  2. Are the assertions as strong as the spec requires? (No weakening — present-but-inert tokens, shape-only checks, status-only error checks.)
22
22
  3. Is database state verification present where the spec requires it?
23
23
 
24
+ ## Regression-guard honesty (`required: false` cases)
25
+
26
+ For EACH acceptance case marked `required: false` in `qa-test-spec.manifest.json`:
27
+ 1. Does it genuinely assert only an **already-satisfied infrastructure invariant** (a config/harness/convention established by a prior story/epic that this story must not regress)? Or is it a **behavioral** case for THIS story's new work, mislabeled non-required to dodge the red gate?
28
+ 2. The test of intent: would an empty checkout of this story's branch (no implementation) make the case pass? If yes, it is a legitimate invariant-guard. If it should only pass *after* this story's code lands, it is behavioral and must be `required: true`.
29
+ 3. A behavioral case marked `required: false` is a spec cheat — it lets unbuilt behavior ship without ever proving red→green. Flag it HIGH: the red gate is blind to it.
30
+
24
31
  ## Output
25
32
 
26
33
  Record every finding in the Pass 3 table. Assign preliminary severity.
@@ -6,7 +6,7 @@
6
6
 
7
7
  The acceptance test IS the spec and (after the green gate) the proof. You author it **before any implementation exists**, and the pipeline enforces the red-green mechanism around it:
8
8
 
9
- - The **RED gate** runs your suite pre-implementation and every required acceptance case must RUN and FAIL. A case that passes against an empty repo is a spec bug (gameable assertion or wrong expectation) and bounces straight back to you.
9
+ - The **RED gate** runs your suite pre-implementation and every *required* acceptance case must RUN and FAIL. A case that passes against an empty repo is a spec bug (gameable assertion or wrong expectation) and bounces straight back to you — **unless** it only asserts an already-satisfied infrastructure invariant, which you mark `required: false` (Step C). Those are regression-guards: exempt from red, still asserted at green.
10
10
  - Your test files are then **frozen by hash**. Dev agents cannot edit them — any change auto-REJECTs to you for arbitration. The implementation must come to the tests; the tests never go to the implementation.
11
11
  - The **GREEN gate** re-runs the suite post-implementation; red evidence + green evidence + the diff SHA become the story's proof object (`proof.json`).
12
12
 
@@ -29,15 +29,24 @@ The Live Smoke Test tables (`api` profile) and UI Integration Smoke rows (`ui` p
29
29
 
30
30
  Run the suite once via the configured command. Verify every new test **executes and fails for the behavioral reason you expect** (assertion/404/missing element). Fix syntax/import/collection errors now — `missing-at-red` failures at the gate waste a rework cycle. On greenfield story 1 where the framework cannot run at all yet, note it: the red gate records `mode: weak` and the green gate compensates.
31
31
 
32
- ## Step C: Manifest entries
32
+ ## Step C: Manifest entries — classify each case (behavioral vs invariant-guard)
33
33
 
34
- In `qa-test-spec.manifest.json` (Step 10), each acceptance case carries:
34
+ In `qa-test-spec.manifest.json` (Step 10), each acceptance case carries a `required` flag. **It is not boilerplate — it encodes whether the case can honestly go red:**
35
+
36
+ - **Behavioral case** (`required: true`, the default) — asserts the NEW behavior this story builds. It MUST fail pre-implementation (an empty/old checkout cannot satisfy it) and pass post-implementation. This is the red→green case.
37
+ - **Invariant-guard case** (`required: false`) — asserts a cross-cutting infrastructure invariant **already established by a prior story/epic** that this story must not regress: e.g. `vitest pool: 'forks'`, a `tests/setup.ts` mock harness, an ESLint rule's presence, ESM `.js` import conventions, an existing directory contract. It is **already green at red-time** (the infra exists on the branch), so forcing it into the red-required set makes red dishonest — the gate rejects it as `premature-pass` and blocks the story with zero implementation done. Mark it `required: false`: still executed and asserted at the GREEN gate (the guard still fires on a regression), but EXEMPT from the red-required set. **Do not delete or weaken the guard — just mark it non-required so red is honest.**
35
38
 
36
39
  ```json
37
40
  { "id": "TC-3.4", "kind": "acceptance", "testFile": "tests/acceptance/{story_id}/cards.spec.ts",
38
41
  "tier": "e2e", "priority": "P0", "assertionTargets": ["422", "cards.title"], "required": true, "ac": ["AC-3"] }
42
+ { "id": "TC-6.1", "kind": "acceptance", "testFile": "tests/acceptance/{story_id}/determinism.spec.ts",
43
+ "tier": "integration", "priority": "P1", "assertionTargets": ["forks", "isolate"], "required": false, "ac": ["AC-6"] }
39
44
  ```
40
45
 
46
+ The second case is a regression-guard — it asserts a pre-existing determinism/isolation invariant, so it is `required: false`.
47
+
48
+ **Litmus test:** if an empty checkout of this story's branch (no implementation) would make the case PASS, it is an invariant-guard → `required: false`. Everything testing what *this* story adds stays `required: true`. Misclassifying a behavioral case as a guard to dodge red is a spec cheat — CRITIC's acceptance audit checks for it, so the only thing you buy by cheating is a rejection.
49
+
41
50
  plus the manifest-level `"files": [...]` list naming EVERY file you wrote (tests + helpers). Cases the dev should implement as unit/integration tests keep `kind: "unit-spec"` (or omit `kind`) — those remain specs, and the dev writes them.
42
51
 
43
52
  ## Step D: What remains prose
@@ -0,0 +1,107 @@
1
+ // valent-pipeline — STANDARD hardened ESLint baseline.
2
+ // Seeded by `valent-pipeline init` into TypeScript projects that have NO ESLint config of their own;
3
+ // it never overwrites an existing one. This is the universal base — add project-specific guards in
4
+ // your own block.
5
+ //
6
+ // What it enforces (ONE `eslint .` pass, prod vs tests differentiated by file glob):
7
+ // • Strict TypeScript correctness — typescript-eslint `strict`, the hardest SYNTACTIC tier — on
8
+ // production code AND tests (a broken test is false confidence).
9
+ // • Maintainability caps (complexity / depth / length) on PRODUCTION code only — off for tests,
10
+ // where long/repetitive is fine.
11
+ //
12
+ // Requires (devDependencies — the standard uses NO others):
13
+ // eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin
14
+ // Recommended package.json scripts:
15
+ // "lint": "eslint . --max-warnings 0" "typecheck": "tsc --noEmit"
16
+ // Make them a blocking gate by adding both to pipeline-config.yaml `pre_critic_gate.commands`.
17
+ //
18
+ // Project-specific guards (e.g. banning Math.random for deterministic sims, NodeNext import-extension
19
+ // rules, module-boundary guards) are intentionally NOT here — add them in your own config block.
20
+ // Type-aware rules (no-floating-promises, …) are the fast-follow — see the commented block at the bottom.
21
+
22
+ import tseslint from '@typescript-eslint/eslint-plugin';
23
+ import tsParser from '@typescript-eslint/parser';
24
+
25
+ // CORRECTNESS — applies to prod AND tests. STRICT = hardest syntactic tier (recommended spread first
26
+ // so the base set is present even if strict's `.rules` is delta-only).
27
+ const correctness = {
28
+ ...tseslint.configs.recommended.rules,
29
+ ...tseslint.configs.strict.rules,
30
+ '@typescript-eslint/no-explicit-any': 'error',
31
+ '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
32
+ eqeqeq: ['error', 'smart'],
33
+ 'no-var': 'error',
34
+ 'prefer-const': 'error',
35
+ 'no-loop-func': 'error',
36
+ '@typescript-eslint/no-empty-function': 'error',
37
+ };
38
+
39
+ // MAINTAINABILITY — PRODUCTION code only (off for tests). Caps start strict; loosen per project if a
40
+ // codebase legitimately needs it, or tighten over time.
41
+ const maintainability = {
42
+ complexity: ['error', { max: 10 }],
43
+ 'max-depth': ['error', { max: 3 }],
44
+ 'max-lines-per-function': ['error', { max: 100, skipBlankLines: true, skipComments: true }],
45
+ 'max-lines': ['error', { max: 500, skipBlankLines: true, skipComments: true }],
46
+ 'no-console': ['error', { allow: ['warn', 'error'] }],
47
+ 'no-warning-comments': ['error', { terms: ['fixme', 'xxx', 'hack'], location: 'anywhere' }],
48
+ 'func-names': ['error', 'as-needed'],
49
+ };
50
+
51
+ // Lenient naming convention — enforces common casing without fighting external/snake_case APIs.
52
+ const naming = ['error',
53
+ { selector: 'default', format: ['camelCase'], leadingUnderscore: 'allow' },
54
+ { selector: 'variable', format: ['camelCase', 'PascalCase', 'UPPER_CASE'], leadingUnderscore: 'allow' },
55
+ { selector: 'typeLike', format: ['PascalCase'] },
56
+ { selector: 'enumMember', format: ['PascalCase', 'UPPER_CASE'] },
57
+ { selector: 'parameter', format: ['camelCase'], leadingUnderscore: 'allow' },
58
+ { selector: 'variable', modifiers: ['destructured'], format: null },
59
+ { selector: 'objectLiteralProperty', format: null },
60
+ ];
61
+
62
+ const tsLang = { parser: tsParser, parserOptions: { ecmaVersion: 2022, sourceType: 'module', ecmaFeatures: { jsx: true } } };
63
+
64
+ export default [
65
+ {
66
+ ignores: ['dist/**', 'build/**', 'node_modules/**', 'coverage/**', 'test-results/**',
67
+ '.valent-pipeline/**', 'stories/**', 'knowledge/**', '**/*.config.{js,cjs,mjs,ts}'],
68
+ },
69
+
70
+ // ── PRODUCTION code: strict correctness + maintainability + naming ──
71
+ {
72
+ files: ['**/*.ts', '**/*.tsx'],
73
+ languageOptions: tsLang,
74
+ plugins: { '@typescript-eslint': tseslint },
75
+ rules: {
76
+ ...correctness,
77
+ ...maintainability,
78
+ '@typescript-eslint/naming-convention': naming,
79
+ },
80
+ },
81
+
82
+ // ── TESTS (dedicated + colocated): correctness ONLY — size/cosmetics off. MUST stay last. ──
83
+ {
84
+ files: ['tests/**/*.ts', 'tests/**/*.tsx', '**/*.test.ts', '**/*.test.tsx', '**/*.spec.ts', '**/*.spec.tsx'],
85
+ languageOptions: tsLang,
86
+ plugins: { '@typescript-eslint': tseslint },
87
+ rules: {
88
+ ...correctness,
89
+ complexity: 'off',
90
+ 'max-depth': 'off',
91
+ 'max-lines': 'off',
92
+ 'max-lines-per-function': 'off',
93
+ 'no-console': 'off',
94
+ 'func-names': 'off',
95
+ 'no-warning-comments': 'off',
96
+ '@typescript-eslint/no-non-null-assertion': 'off', // `!` is idiomatic in test assertions
97
+ },
98
+ },
99
+ ];
100
+
101
+ /* ════ TYPE-AWARE FAST-FOLLOW — the eventual ceiling (strict-type-checked). Requires type info.
102
+ After `npm i -D typescript-eslint` (the unified pkg) and a tsconfig that includes your source:
103
+ • PRODUCTION block: parserOptions.projectService = true; spread ...strictTypeChecked (scoped) —
104
+ adds no-floating-promises, no-misused-promises, await-thenable, no-unnecessary-condition, …
105
+ • TESTS block: add '@typescript-eslint/no-floating-promises':'error' + no-misused-promises — the
106
+ highest-value test rules (they catch false-green un-awaited async assertions).
107
+ Needs a typescript-eslint release that supports your TypeScript major. ════ */
@@ -105,6 +105,27 @@ export async function init(options = {}) {
105
105
  console.log(' Created knowledge/correction-directives.yaml');
106
106
  }
107
107
 
108
+ // 6b. Seed the standard hardened ESLint config for TypeScript projects — inherited ONLY when the
109
+ // project has no ESLint config of its own (never clobbers an existing setup). The standard uses
110
+ // just @typescript-eslint/{parser,eslint-plugin}; see the config header for the lint/typecheck
111
+ // scripts to wire into pre_critic_gate.commands.
112
+ const projectLanguage = (config.tech_stack?.language || 'TypeScript').toLowerCase();
113
+ const isTsProject = projectLanguage.includes('typescript') || /\bts\b/.test(projectLanguage);
114
+ if (isTsProject) {
115
+ const eslintConfigNames = [
116
+ 'eslint.config.js', 'eslint.config.mjs', 'eslint.config.cjs', 'eslint.config.ts',
117
+ '.eslintrc', '.eslintrc.js', '.eslintrc.cjs', '.eslintrc.json', '.eslintrc.yml', '.eslintrc.yaml',
118
+ ];
119
+ const hasEslintConfig = eslintConfigNames.some((n) => fileExists(join(projectRoot, n)));
120
+ const stdEslintSrc = join(PACKAGE_ROOT, 'pipeline', 'templates', 'eslint.config.standard.mjs');
121
+ if (hasEslintConfig) {
122
+ console.log(' ESLint config already present — kept the project\'s own (standard not seeded)');
123
+ } else if (existsSync(stdEslintSrc)) {
124
+ writeFileSafe(join(projectRoot, 'eslint.config.mjs'), readFileSync(stdEslintSrc, 'utf-8'));
125
+ console.log(' Seeded standard hardened ESLint config -> eslint.config.mjs');
126
+ }
127
+ }
128
+
108
129
  // 7. Write the vendored CLI's runtime package.json and install its deps. The CLI needs
109
130
  // commander/yaml/ajv to run AT ALL (resolve-graph, validate-handoff, sprint-pack, …);
110
131
  // sqlite knowledge mode additionally needs better-sqlite3 (FTS5 is compiled in — the whole