@syntesseraai/opencode-feature-factory 0.2.7 → 0.2.8
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/dist/discovery.js +19 -16
- package/dist/discovery.test.js +2 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +53 -50
- package/dist/output.js +6 -2
- package/dist/output.test.js +30 -28
- package/dist/quality-gate-config.js +14 -6
- package/dist/quality-gate-config.test.js +24 -22
- package/dist/stop-quality-gate.d.ts +1 -1
- package/dist/stop-quality-gate.js +11 -5
- package/dist/stop-quality-gate.test.js +84 -82
- package/dist/types.js +2 -1
- package/package.json +1 -10
package/dist/discovery.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveCommands = resolveCommands;
|
|
4
|
+
const quality_gate_config_1 = require("./quality-gate-config");
|
|
2
5
|
// ============================================================================
|
|
3
6
|
// Package Manager Detection
|
|
4
7
|
// ============================================================================
|
|
@@ -11,15 +14,15 @@ async function detectPackageManager($, directory, override) {
|
|
|
11
14
|
return override;
|
|
12
15
|
}
|
|
13
16
|
// Priority order: pnpm > bun > yarn > npm
|
|
14
|
-
if (await fileExists($, `${directory}/pnpm-lock.yaml`))
|
|
17
|
+
if (await (0, quality_gate_config_1.fileExists)($, `${directory}/pnpm-lock.yaml`))
|
|
15
18
|
return 'pnpm';
|
|
16
|
-
if (await fileExists($, `${directory}/bun.lockb`))
|
|
19
|
+
if (await (0, quality_gate_config_1.fileExists)($, `${directory}/bun.lockb`))
|
|
17
20
|
return 'bun';
|
|
18
|
-
if (await fileExists($, `${directory}/bun.lock`))
|
|
21
|
+
if (await (0, quality_gate_config_1.fileExists)($, `${directory}/bun.lock`))
|
|
19
22
|
return 'bun';
|
|
20
|
-
if (await fileExists($, `${directory}/yarn.lock`))
|
|
23
|
+
if (await (0, quality_gate_config_1.fileExists)($, `${directory}/yarn.lock`))
|
|
21
24
|
return 'yarn';
|
|
22
|
-
if (await fileExists($, `${directory}/package-lock.json`))
|
|
25
|
+
if (await (0, quality_gate_config_1.fileExists)($, `${directory}/package-lock.json`))
|
|
23
26
|
return 'npm';
|
|
24
27
|
return 'npm'; // fallback
|
|
25
28
|
}
|
|
@@ -45,7 +48,7 @@ function buildRunCommand(pm, script) {
|
|
|
45
48
|
* Discover Node.js lint/build/test commands from package.json scripts
|
|
46
49
|
*/
|
|
47
50
|
async function discoverNodeCommands($, directory, pm) {
|
|
48
|
-
const pkgJson = await readJsonFile($, `${directory}/package.json`);
|
|
51
|
+
const pkgJson = await (0, quality_gate_config_1.readJsonFile)($, `${directory}/package.json`);
|
|
49
52
|
if (!pkgJson?.scripts)
|
|
50
53
|
return [];
|
|
51
54
|
const scripts = pkgJson.scripts;
|
|
@@ -74,7 +77,7 @@ async function discoverNodeCommands($, directory, pm) {
|
|
|
74
77
|
* Discover Rust commands from Cargo.toml presence
|
|
75
78
|
*/
|
|
76
79
|
async function discoverRustCommands($, directory, includeClippy) {
|
|
77
|
-
if (!(await fileExists($, `${directory}/Cargo.toml`)))
|
|
80
|
+
if (!(await (0, quality_gate_config_1.fileExists)($, `${directory}/Cargo.toml`)))
|
|
78
81
|
return [];
|
|
79
82
|
const steps = [{ step: 'lint (fmt)', cmd: 'cargo fmt --check' }];
|
|
80
83
|
if (includeClippy) {
|
|
@@ -94,7 +97,7 @@ async function discoverRustCommands($, directory, includeClippy) {
|
|
|
94
97
|
* Discover Go commands from go.mod presence
|
|
95
98
|
*/
|
|
96
99
|
async function discoverGoCommands($, directory) {
|
|
97
|
-
if (!(await fileExists($, `${directory}/go.mod`)))
|
|
100
|
+
if (!(await (0, quality_gate_config_1.fileExists)($, `${directory}/go.mod`)))
|
|
98
101
|
return [];
|
|
99
102
|
return [{ step: 'test', cmd: 'go test ./...' }];
|
|
100
103
|
}
|
|
@@ -106,8 +109,8 @@ async function discoverGoCommands($, directory) {
|
|
|
106
109
|
*/
|
|
107
110
|
async function discoverPythonCommands($, directory) {
|
|
108
111
|
// Only add pytest if we have strong signal
|
|
109
|
-
const hasPytestIni = await fileExists($, `${directory}/pytest.ini`);
|
|
110
|
-
const hasPyproject = await fileExists($, `${directory}/pyproject.toml`);
|
|
112
|
+
const hasPytestIni = await (0, quality_gate_config_1.fileExists)($, `${directory}/pytest.ini`);
|
|
113
|
+
const hasPyproject = await (0, quality_gate_config_1.fileExists)($, `${directory}/pyproject.toml`);
|
|
111
114
|
if (!hasPytestIni && !hasPyproject)
|
|
112
115
|
return [];
|
|
113
116
|
// pytest.ini is strong signal
|
|
@@ -142,11 +145,11 @@ async function discoverPythonCommands($, directory) {
|
|
|
142
145
|
*
|
|
143
146
|
* @returns Array of command steps to execute, or empty if nothing found
|
|
144
147
|
*/
|
|
145
|
-
|
|
148
|
+
async function resolveCommands(args) {
|
|
146
149
|
const { $, directory, config } = args;
|
|
147
|
-
const mergedConfig = { ...DEFAULT_QUALITY_GATE, ...config };
|
|
150
|
+
const mergedConfig = { ...quality_gate_config_1.DEFAULT_QUALITY_GATE, ...config };
|
|
148
151
|
// 1. Configured commands take priority (do NOT run ci.sh if these exist)
|
|
149
|
-
if (hasConfiguredCommands(config)) {
|
|
152
|
+
if ((0, quality_gate_config_1.hasConfiguredCommands)(config)) {
|
|
150
153
|
const steps = [];
|
|
151
154
|
const order = mergedConfig.steps;
|
|
152
155
|
for (const stepName of order) {
|
|
@@ -160,14 +163,14 @@ export async function resolveCommands(args) {
|
|
|
160
163
|
// 2. Feature Factory CI script (only if no configured commands)
|
|
161
164
|
if (mergedConfig.useCiSh !== 'never') {
|
|
162
165
|
const ciShPath = `${directory}/management/ci.sh`;
|
|
163
|
-
if (await fileExists($, ciShPath)) {
|
|
166
|
+
if (await (0, quality_gate_config_1.fileExists)($, ciShPath)) {
|
|
164
167
|
return [{ step: 'ci', cmd: `bash ${ciShPath}` }];
|
|
165
168
|
}
|
|
166
169
|
}
|
|
167
170
|
// 3. Conventional discovery (only if no ci.sh)
|
|
168
171
|
const pm = await detectPackageManager($, directory, mergedConfig.packageManager);
|
|
169
172
|
// Try Node first (most common)
|
|
170
|
-
if (await fileExists($, `${directory}/package.json`)) {
|
|
173
|
+
if (await (0, quality_gate_config_1.fileExists)($, `${directory}/package.json`)) {
|
|
171
174
|
const nodeSteps = await discoverNodeCommands($, directory, pm);
|
|
172
175
|
if (nodeSteps.length > 0)
|
|
173
176
|
return nodeSteps;
|
package/dist/discovery.test.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
/**
|
|
2
3
|
* Unit tests for discovery module
|
|
3
4
|
*
|
|
@@ -7,6 +8,7 @@
|
|
|
7
8
|
* Note: Most functions in discovery.ts require shell access ($) so they're
|
|
8
9
|
* integration-tested elsewhere. This file tests the pure utility functions.
|
|
9
10
|
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
12
|
// Re-implement buildRunCommand for testing since it's not exported
|
|
11
13
|
function buildRunCommand(pm, script) {
|
|
12
14
|
switch (pm) {
|
|
@@ -92,4 +94,3 @@ describe('PackageManager type', () => {
|
|
|
92
94
|
});
|
|
93
95
|
});
|
|
94
96
|
});
|
|
95
|
-
export {};
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
3
|
+
exports.StopQualityGatePlugin = exports.AGENT_PATHS = exports.SKILL_PATHS = void 0;
|
|
4
|
+
const stop_quality_gate_1 = require('./stop-quality-gate');
|
|
2
5
|
// Export skill and agent paths for programmatic access
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
exports.SKILL_PATHS = {
|
|
7
|
+
'ff-mini-plan': './skills/ff-mini-plan/SKILL.md',
|
|
8
|
+
'ff-todo-management': './skills/ff-todo-management/SKILL.md',
|
|
9
|
+
'ff-severity-classification': './skills/ff-severity-classification/SKILL.md',
|
|
10
|
+
'ff-report-templates': './skills/ff-report-templates/SKILL.md',
|
|
8
11
|
};
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
exports.AGENT_PATHS = {
|
|
13
|
+
'ff-acceptance': './agents/ff-acceptance.md',
|
|
14
|
+
'ff-review': './agents/ff-review.md',
|
|
15
|
+
'ff-security': './agents/ff-security.md',
|
|
16
|
+
'ff-well-architected': './agents/ff-well-architected.md',
|
|
17
|
+
'ff-validate': './agents/ff-validate.md',
|
|
15
18
|
};
|
|
16
19
|
const SERVICE_NAME = 'feature-factory';
|
|
17
20
|
/**
|
|
@@ -19,29 +22,27 @@ const SERVICE_NAME = 'feature-factory';
|
|
|
19
22
|
* Silently fails if logging is unavailable.
|
|
20
23
|
*/
|
|
21
24
|
async function log(client, level, message, extra) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
25
|
+
try {
|
|
26
|
+
await client.app.log({
|
|
27
|
+
body: {
|
|
28
|
+
service: SERVICE_NAME,
|
|
29
|
+
level,
|
|
30
|
+
message,
|
|
31
|
+
extra,
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
} catch {
|
|
35
|
+
// Logging failure should not affect plugin operation
|
|
36
|
+
}
|
|
35
37
|
}
|
|
36
38
|
/**
|
|
37
39
|
* Determine the project root directory.
|
|
38
40
|
* Prefer worktree (git root) if available, otherwise use directory.
|
|
39
41
|
*/
|
|
40
42
|
function resolveRootDir(input) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return input.directory;
|
|
43
|
+
const worktree = (input.worktree ?? '').trim();
|
|
44
|
+
if (worktree.length > 0) return worktree;
|
|
45
|
+
return input.directory;
|
|
45
46
|
}
|
|
46
47
|
/**
|
|
47
48
|
* Stop Quality Gate Plugin
|
|
@@ -56,26 +57,28 @@ function resolveRootDir(input) {
|
|
|
56
57
|
* - On failure: passes full CI output to LLM for fix instructions
|
|
57
58
|
* - If management/ci.sh does not exist, quality gate does not run
|
|
58
59
|
*/
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
};
|
|
60
|
+
const StopQualityGatePlugin = async (input) => {
|
|
61
|
+
const { worktree, directory, client } = input;
|
|
62
|
+
const rootDir = resolveRootDir({ worktree, directory });
|
|
63
|
+
// Skip quality gate if no valid directory (e.g., global config with no project)
|
|
64
|
+
if (!rootDir || rootDir === '' || rootDir === '/') {
|
|
65
|
+
return {};
|
|
66
|
+
}
|
|
67
|
+
// Create quality gate hooks
|
|
68
|
+
let qualityGateHooks = {};
|
|
69
|
+
try {
|
|
70
|
+
qualityGateHooks = await (0, stop_quality_gate_1.createQualityGateHooks)(input);
|
|
71
|
+
} catch (error) {
|
|
72
|
+
await log(client, 'error', 'quality-gate.init-error', {
|
|
73
|
+
error: String(error),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
...qualityGateHooks,
|
|
78
|
+
};
|
|
79
79
|
};
|
|
80
|
+
exports.StopQualityGatePlugin = StopQualityGatePlugin;
|
|
80
81
|
// Default export for OpenCode plugin discovery
|
|
81
|
-
|
|
82
|
+
exports.default = exports.StopQualityGatePlugin;
|
|
83
|
+
// Make module directly callable for CommonJS compatibility
|
|
84
|
+
module.exports = StopQualityGatePlugin;
|
package/dist/output.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.extractErrorLines = extractErrorLines;
|
|
4
|
+
exports.tailLines = tailLines;
|
|
1
5
|
/**
|
|
2
6
|
* Regex pattern for detecting error-like lines in command output
|
|
3
7
|
*/
|
|
@@ -10,7 +14,7 @@ const ERROR_PATTERNS = /(error|failed|failure|panic|assert|exception|traceback|T
|
|
|
10
14
|
* @param maxLines - Maximum number of error lines to extract
|
|
11
15
|
* @returns Array of error-like lines (up to maxLines)
|
|
12
16
|
*/
|
|
13
|
-
|
|
17
|
+
function extractErrorLines(output, maxLines) {
|
|
14
18
|
const lines = output.split('\n');
|
|
15
19
|
const errorLines = [];
|
|
16
20
|
for (let i = 0; i < lines.length && errorLines.length < maxLines; i++) {
|
|
@@ -37,7 +41,7 @@ export function extractErrorLines(output, maxLines) {
|
|
|
37
41
|
* @param maxLines - Maximum number of lines to return
|
|
38
42
|
* @returns Truncated output with notice, or full output if short enough
|
|
39
43
|
*/
|
|
40
|
-
|
|
44
|
+
function tailLines(output, maxLines) {
|
|
41
45
|
const lines = output.split('\n');
|
|
42
46
|
if (lines.length <= maxLines) {
|
|
43
47
|
return output;
|
package/dist/output.test.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
/**
|
|
2
3
|
* Unit tests for output module
|
|
3
4
|
*
|
|
@@ -5,10 +6,11 @@
|
|
|
5
6
|
* - extractErrorLines: extracts error-like lines from command output
|
|
6
7
|
* - tailLines: returns last N lines with truncation notice
|
|
7
8
|
*/
|
|
8
|
-
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
const output_1 = require("./output");
|
|
9
11
|
describe('extractErrorLines', () => {
|
|
10
12
|
it('should return empty array for empty input', () => {
|
|
11
|
-
expect(extractErrorLines('', 10)).toEqual([]);
|
|
13
|
+
expect((0, output_1.extractErrorLines)('', 10)).toEqual([]);
|
|
12
14
|
});
|
|
13
15
|
it('should return empty array when no errors found', () => {
|
|
14
16
|
const output = `
|
|
@@ -16,7 +18,7 @@ Build started
|
|
|
16
18
|
Compiling modules...
|
|
17
19
|
Build completed successfully
|
|
18
20
|
`;
|
|
19
|
-
expect(extractErrorLines(output, 10)).toEqual([]);
|
|
21
|
+
expect((0, output_1.extractErrorLines)(output, 10)).toEqual([]);
|
|
20
22
|
});
|
|
21
23
|
it('should extract lines containing "error"', () => {
|
|
22
24
|
const output = `
|
|
@@ -24,7 +26,7 @@ Starting build
|
|
|
24
26
|
error: Cannot find module 'missing'
|
|
25
27
|
Build failed
|
|
26
28
|
`;
|
|
27
|
-
const result = extractErrorLines(output, 10);
|
|
29
|
+
const result = (0, output_1.extractErrorLines)(output, 10);
|
|
28
30
|
expect(result.some((line) => line.includes('error:'))).toBe(true);
|
|
29
31
|
});
|
|
30
32
|
it('should extract lines containing "Error" (case insensitive)', () => {
|
|
@@ -33,7 +35,7 @@ Running tests
|
|
|
33
35
|
TypeError: undefined is not a function
|
|
34
36
|
at test.js:10:5
|
|
35
37
|
`;
|
|
36
|
-
const result = extractErrorLines(output, 10);
|
|
38
|
+
const result = (0, output_1.extractErrorLines)(output, 10);
|
|
37
39
|
expect(result.some((line) => line.includes('TypeError'))).toBe(true);
|
|
38
40
|
});
|
|
39
41
|
it('should extract lines containing "failed"', () => {
|
|
@@ -42,7 +44,7 @@ Test suite: MyTests
|
|
|
42
44
|
Test failed: should work correctly
|
|
43
45
|
1 test failed
|
|
44
46
|
`;
|
|
45
|
-
const result = extractErrorLines(output, 10);
|
|
47
|
+
const result = (0, output_1.extractErrorLines)(output, 10);
|
|
46
48
|
expect(result.some((line) => line.toLowerCase().includes('failed'))).toBe(true);
|
|
47
49
|
});
|
|
48
50
|
it('should extract lines containing "FAILED"', () => {
|
|
@@ -50,7 +52,7 @@ Test failed: should work correctly
|
|
|
50
52
|
Running: test_function
|
|
51
53
|
FAILED test_function - assertion error
|
|
52
54
|
`;
|
|
53
|
-
const result = extractErrorLines(output, 10);
|
|
55
|
+
const result = (0, output_1.extractErrorLines)(output, 10);
|
|
54
56
|
expect(result.some((line) => line.includes('FAILED'))).toBe(true);
|
|
55
57
|
});
|
|
56
58
|
it('should extract lines containing "panic"', () => {
|
|
@@ -58,7 +60,7 @@ FAILED test_function - assertion error
|
|
|
58
60
|
thread 'main' panicked at 'assertion failed'
|
|
59
61
|
note: run with RUST_BACKTRACE=1
|
|
60
62
|
`;
|
|
61
|
-
const result = extractErrorLines(output, 10);
|
|
63
|
+
const result = (0, output_1.extractErrorLines)(output, 10);
|
|
62
64
|
expect(result.some((line) => line.includes('panic'))).toBe(true);
|
|
63
65
|
});
|
|
64
66
|
it('should extract lines containing "exception"', () => {
|
|
@@ -66,7 +68,7 @@ note: run with RUST_BACKTRACE=1
|
|
|
66
68
|
Exception in thread "main" java.lang.NullPointerException
|
|
67
69
|
at Main.main(Main.java:5)
|
|
68
70
|
`;
|
|
69
|
-
const result = extractErrorLines(output, 10);
|
|
71
|
+
const result = (0, output_1.extractErrorLines)(output, 10);
|
|
70
72
|
expect(result.some((line) => line.includes('Exception'))).toBe(true);
|
|
71
73
|
});
|
|
72
74
|
it('should extract lines containing "traceback"', () => {
|
|
@@ -75,7 +77,7 @@ Traceback (most recent call last):
|
|
|
75
77
|
File "test.py", line 10
|
|
76
78
|
NameError: name 'x' is not defined
|
|
77
79
|
`;
|
|
78
|
-
const result = extractErrorLines(output, 10);
|
|
80
|
+
const result = (0, output_1.extractErrorLines)(output, 10);
|
|
79
81
|
expect(result.some((line) => line.includes('Traceback'))).toBe(true);
|
|
80
82
|
});
|
|
81
83
|
it('should include 2 lines of context after error', () => {
|
|
@@ -84,7 +86,7 @@ error: something went wrong
|
|
|
84
86
|
context line 1
|
|
85
87
|
context line 2
|
|
86
88
|
line5`;
|
|
87
|
-
const result = extractErrorLines(output, 10);
|
|
89
|
+
const result = (0, output_1.extractErrorLines)(output, 10);
|
|
88
90
|
expect(result).toContain('error: something went wrong');
|
|
89
91
|
expect(result).toContain('context line 1');
|
|
90
92
|
expect(result).toContain('context line 2');
|
|
@@ -101,36 +103,36 @@ error: third error
|
|
|
101
103
|
context5
|
|
102
104
|
context6
|
|
103
105
|
`;
|
|
104
|
-
const result = extractErrorLines(output, 5);
|
|
106
|
+
const result = (0, output_1.extractErrorLines)(output, 5);
|
|
105
107
|
expect(result.length).toBeLessThanOrEqual(5);
|
|
106
108
|
});
|
|
107
109
|
it('should handle error at end of output without enough context lines', () => {
|
|
108
110
|
const output = `line1
|
|
109
111
|
line2
|
|
110
112
|
error: final error`;
|
|
111
|
-
const result = extractErrorLines(output, 10);
|
|
113
|
+
const result = (0, output_1.extractErrorLines)(output, 10);
|
|
112
114
|
expect(result).toContain('error: final error');
|
|
113
115
|
});
|
|
114
116
|
it('should extract ReferenceError', () => {
|
|
115
117
|
const output = `ReferenceError: x is not defined`;
|
|
116
|
-
const result = extractErrorLines(output, 10);
|
|
118
|
+
const result = (0, output_1.extractErrorLines)(output, 10);
|
|
117
119
|
expect(result).toContain('ReferenceError: x is not defined');
|
|
118
120
|
});
|
|
119
121
|
it('should extract SyntaxError', () => {
|
|
120
122
|
const output = `SyntaxError: Unexpected token '}'`;
|
|
121
|
-
const result = extractErrorLines(output, 10);
|
|
123
|
+
const result = (0, output_1.extractErrorLines)(output, 10);
|
|
122
124
|
expect(result).toContain("SyntaxError: Unexpected token '}'");
|
|
123
125
|
});
|
|
124
126
|
it('should extract ERR! (npm style errors)', () => {
|
|
125
127
|
const output = `npm ERR! code ENOENT
|
|
126
128
|
npm ERR! path /app/package.json`;
|
|
127
|
-
const result = extractErrorLines(output, 10);
|
|
129
|
+
const result = (0, output_1.extractErrorLines)(output, 10);
|
|
128
130
|
expect(result.some((line) => line.includes('ERR!'))).toBe(true);
|
|
129
131
|
});
|
|
130
132
|
it('should extract FAIL (test runner style)', () => {
|
|
131
133
|
const output = `FAIL src/test.ts
|
|
132
134
|
Test suite failed to run`;
|
|
133
|
-
const result = extractErrorLines(output, 10);
|
|
135
|
+
const result = (0, output_1.extractErrorLines)(output, 10);
|
|
134
136
|
expect(result.some((line) => line.includes('FAIL'))).toBe(true);
|
|
135
137
|
});
|
|
136
138
|
it('should extract multiple error types in same output', () => {
|
|
@@ -141,7 +143,7 @@ TypeError: Cannot read property 'x'
|
|
|
141
143
|
FAILED: test_something
|
|
142
144
|
Build process ended
|
|
143
145
|
`;
|
|
144
|
-
const result = extractErrorLines(output, 20);
|
|
146
|
+
const result = (0, output_1.extractErrorLines)(output, 20);
|
|
145
147
|
expect(result.some((line) => line.includes('error:'))).toBe(true);
|
|
146
148
|
expect(result.some((line) => line.includes('TypeError'))).toBe(true);
|
|
147
149
|
expect(result.some((line) => line.includes('FAILED'))).toBe(true);
|
|
@@ -150,15 +152,15 @@ Build process ended
|
|
|
150
152
|
describe('tailLines', () => {
|
|
151
153
|
it('should return full output when lines count is less than maxLines', () => {
|
|
152
154
|
const output = 'line1\nline2\nline3';
|
|
153
|
-
expect(tailLines(output, 10)).toBe(output);
|
|
155
|
+
expect((0, output_1.tailLines)(output, 10)).toBe(output);
|
|
154
156
|
});
|
|
155
157
|
it('should return full output when lines count equals maxLines', () => {
|
|
156
158
|
const output = 'line1\nline2\nline3';
|
|
157
|
-
expect(tailLines(output, 3)).toBe(output);
|
|
159
|
+
expect((0, output_1.tailLines)(output, 3)).toBe(output);
|
|
158
160
|
});
|
|
159
161
|
it('should truncate and add notice when output exceeds maxLines', () => {
|
|
160
162
|
const output = 'line1\nline2\nline3\nline4\nline5';
|
|
161
|
-
const result = tailLines(output, 3);
|
|
163
|
+
const result = (0, output_1.tailLines)(output, 3);
|
|
162
164
|
expect(result).toContain('... (2 lines truncated)');
|
|
163
165
|
expect(result).toContain('line3');
|
|
164
166
|
expect(result).toContain('line4');
|
|
@@ -168,38 +170,38 @@ describe('tailLines', () => {
|
|
|
168
170
|
});
|
|
169
171
|
it('should handle single line output', () => {
|
|
170
172
|
const output = 'single line';
|
|
171
|
-
expect(tailLines(output, 5)).toBe('single line');
|
|
173
|
+
expect((0, output_1.tailLines)(output, 5)).toBe('single line');
|
|
172
174
|
});
|
|
173
175
|
it('should handle empty output', () => {
|
|
174
|
-
expect(tailLines('', 5)).toBe('');
|
|
176
|
+
expect((0, output_1.tailLines)('', 5)).toBe('');
|
|
175
177
|
});
|
|
176
178
|
it('should handle maxLines of 1', () => {
|
|
177
179
|
const output = 'line1\nline2\nline3';
|
|
178
|
-
const result = tailLines(output, 1);
|
|
180
|
+
const result = (0, output_1.tailLines)(output, 1);
|
|
179
181
|
expect(result).toContain('... (2 lines truncated)');
|
|
180
182
|
expect(result).toContain('line3');
|
|
181
183
|
});
|
|
182
184
|
it('should correctly count truncated lines', () => {
|
|
183
185
|
const lines = Array.from({ length: 100 }, (_, i) => `line${i + 1}`);
|
|
184
186
|
const output = lines.join('\n');
|
|
185
|
-
const result = tailLines(output, 10);
|
|
187
|
+
const result = (0, output_1.tailLines)(output, 10);
|
|
186
188
|
expect(result).toContain('... (90 lines truncated)');
|
|
187
189
|
});
|
|
188
190
|
it('should preserve line content in tail', () => {
|
|
189
191
|
const output = 'first\nsecond\nthird\nfourth\nfifth';
|
|
190
|
-
const result = tailLines(output, 2);
|
|
192
|
+
const result = (0, output_1.tailLines)(output, 2);
|
|
191
193
|
const resultLines = result.split('\n');
|
|
192
194
|
expect(resultLines[resultLines.length - 1]).toBe('fifth');
|
|
193
195
|
expect(resultLines[resultLines.length - 2]).toBe('fourth');
|
|
194
196
|
});
|
|
195
197
|
it('should handle output with empty lines', () => {
|
|
196
198
|
const output = 'line1\n\nline3\n\nline5';
|
|
197
|
-
const result = tailLines(output, 3);
|
|
199
|
+
const result = (0, output_1.tailLines)(output, 3);
|
|
198
200
|
expect(result).toContain('... (2 lines truncated)');
|
|
199
201
|
});
|
|
200
202
|
it('should handle output with only newlines', () => {
|
|
201
203
|
const output = '\n\n\n\n';
|
|
202
|
-
const result = tailLines(output, 2);
|
|
204
|
+
const result = (0, output_1.tailLines)(output, 2);
|
|
203
205
|
expect(result).toContain('... (3 lines truncated)');
|
|
204
206
|
});
|
|
205
207
|
});
|
|
@@ -1,7 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_QUALITY_GATE = void 0;
|
|
4
|
+
exports.readJsonFile = readJsonFile;
|
|
5
|
+
exports.fileExists = fileExists;
|
|
6
|
+
exports.mergeQualityGateConfig = mergeQualityGateConfig;
|
|
7
|
+
exports.loadQualityGateConfig = loadQualityGateConfig;
|
|
8
|
+
exports.hasConfiguredCommands = hasConfiguredCommands;
|
|
1
9
|
/**
|
|
2
10
|
* Default configuration values for StopQualityGate
|
|
3
11
|
*/
|
|
4
|
-
|
|
12
|
+
exports.DEFAULT_QUALITY_GATE = {
|
|
5
13
|
steps: ['lint', 'build', 'test'],
|
|
6
14
|
useCiSh: 'auto',
|
|
7
15
|
packageManager: 'auto',
|
|
@@ -15,7 +23,7 @@ export const DEFAULT_QUALITY_GATE = {
|
|
|
15
23
|
/**
|
|
16
24
|
* Read a JSON file safely, returning null if not found or invalid
|
|
17
25
|
*/
|
|
18
|
-
|
|
26
|
+
async function readJsonFile($, path) {
|
|
19
27
|
try {
|
|
20
28
|
const result = await $ `cat ${path}`.quiet();
|
|
21
29
|
return JSON.parse(result.text());
|
|
@@ -27,7 +35,7 @@ export async function readJsonFile($, path) {
|
|
|
27
35
|
/**
|
|
28
36
|
* Check if a file exists
|
|
29
37
|
*/
|
|
30
|
-
|
|
38
|
+
async function fileExists($, path) {
|
|
31
39
|
try {
|
|
32
40
|
await $ `test -f ${path}`.quiet();
|
|
33
41
|
return true;
|
|
@@ -42,7 +50,7 @@ export async function fileExists($, path) {
|
|
|
42
50
|
* Precedence: `.opencode/opencode.json` overrides `opencode.json` (root).
|
|
43
51
|
* Deep-merges nested objects like `include`.
|
|
44
52
|
*/
|
|
45
|
-
|
|
53
|
+
function mergeQualityGateConfig(root, dotOpencode) {
|
|
46
54
|
const base = root ?? {};
|
|
47
55
|
const override = dotOpencode ?? {};
|
|
48
56
|
return {
|
|
@@ -64,7 +72,7 @@ export function mergeQualityGateConfig(root, dotOpencode) {
|
|
|
64
72
|
*
|
|
65
73
|
* Merges ONLY the `qualityGate` key. `.opencode/opencode.json` takes precedence.
|
|
66
74
|
*/
|
|
67
|
-
|
|
75
|
+
async function loadQualityGateConfig($, directory) {
|
|
68
76
|
const rootConfigPath = `${directory}/opencode.json`;
|
|
69
77
|
const dotOpencodeConfigPath = `${directory}/.opencode/opencode.json`;
|
|
70
78
|
const [rootJson, dotOpencodeJson] = await Promise.all([
|
|
@@ -79,6 +87,6 @@ export async function loadQualityGateConfig($, directory) {
|
|
|
79
87
|
* Check if any explicit commands are configured (lint/build/test).
|
|
80
88
|
* If so, these take priority over ci.sh and discovery.
|
|
81
89
|
*/
|
|
82
|
-
|
|
90
|
+
function hasConfiguredCommands(config) {
|
|
83
91
|
return !!(config.lint || config.build || config.test);
|
|
84
92
|
}
|