valent-pipeline 0.19.23 → 0.19.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "valent-pipeline",
3
- "version": "0.19.23",
3
+ "version": "0.19.25",
4
4
  "description": "v3 multi-agent AI pipeline for software development lifecycle",
5
5
  "type": "module",
6
6
  "bin": {
@@ -2030,11 +2030,30 @@ async function runStory(story) {
2030
2030
  }
2031
2031
  }
2032
2032
 
2033
+ // Shift-left pre-handoff self-gate (review 2026-06-15): the dev fan-out used to hand off WITHOUT
2034
+ // knowing which deterministic checks the pre-CRITIC STATIC gate would run the instant it did — so
2035
+ // lint/type failures and un-waived test skips reached the gate and cost a full rework round-trip
2036
+ // (a re-spawn), the dominant avoidable-rework pattern in the field (a soft correction-directive
2037
+ // reduced it but did not close it). Thread the gate's OWN checks into the build prompt so the dev
2038
+ // runs them IN its build turn (same context, no re-spawn) and hands off clean. The gate
2039
+ // (runPreCriticGate) stays the mechanical backstop; this just makes it pass first-try, cutting both
2040
+ // the round-trip cost and the rework-cycle count. Same preCriticChecks the gate runs; the
2041
+ // skip-waiver / spec-conformance rules are always named. Static + args only => journal-replay safe.
2042
+ const preHandoffStaticCmds = preCriticChecks.map((c) => subCmd(c, storyId, effectiveWorkspaces))
2043
+ const preHandoffNote = (preHandoffStaticCmds.length || specConformanceEnabled || traceCheckEnabled)
2044
+ ? '\n\n## Definition of done — run these BEFORE you write your handoff\n' +
2045
+ 'The deterministic pre-CRITIC gate runs the moment you hand off; a failure costs a full rework cycle (a re-spawn). Pass them first-try:\n' +
2046
+ preHandoffStaticCmds.map((c) => ` - \`${c}\` — must exit 0 (lint / type / static).\n`).join('') +
2047
+ ' - Every deliberately skipped or pending test MUST carry an inline `// valent-waiver: <reason>` (on the skip line or in the test name) — an un-waived skip is rejected by the spec-conformance check.\n' +
2048
+ ' - Do NOT drop a spec\'d assertion target or omit a spec\'d case to make a suite pass.\n' +
2049
+ 'Run them locally, fix EVERYTHING they flag, and only then write your handoff — this is part of the build, not the gate\'s job to find for you.'
2050
+ : ''
2051
+
2033
2052
  phase('Build')
2034
2053
  // Genuine barrier: CRITIC needs ALL active dev agents' work before reviewing.
2035
2054
  const builds = await parallel(
2036
2055
  devTasks.map((t) => () =>
2037
- spawn(t.agent, `${t.agent.toLowerCase()}.md`, (t.subject || 'Implement production code and tests per the brief and test spec.') + atddReadOnlyNote, {
2056
+ spawn(t.agent, `${t.agent.toLowerCase()}.md`, (t.subject || 'Implement production code and tests per the brief and test spec.') + atddReadOnlyNote + preHandoffNote, {
2038
2057
  label: `build:${t.ref}:${storyId}`,
2039
2058
  phase: 'Build',
2040
2059
  })),
@@ -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