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
|
@@ -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. ════ */
|
package/src/commands/init.js
CHANGED
|
@@ -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
|