@syntesseraai/opencode-feature-factory 0.2.7 → 0.2.9

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 CHANGED
@@ -1,4 +1,7 @@
1
- import { DEFAULT_QUALITY_GATE, fileExists, hasConfiguredCommands, readJsonFile, } from './quality-gate-config';
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
- export async function resolveCommands(args) {
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;
@@ -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
@@ -1,4 +1,6 @@
1
- import type { Plugin } from '@opencode-ai/plugin';
1
+ import type { Plugin } from '@opencode-ai/plugin' with {
2
+ 'resolution-mode': 'import'
3
+ };
2
4
  export declare const SKILL_PATHS: {
3
5
  'ff-mini-plan': string;
4
6
  'ff-todo-management': string;
package/dist/index.js CHANGED
@@ -1,12 +1,15 @@
1
- import { createQualityGateHooks } from './stop-quality-gate';
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
- export const SKILL_PATHS = {
6
+ exports.SKILL_PATHS = {
4
7
  'ff-mini-plan': './skills/ff-mini-plan/SKILL.md',
5
8
  'ff-todo-management': './skills/ff-todo-management/SKILL.md',
6
9
  'ff-severity-classification': './skills/ff-severity-classification/SKILL.md',
7
10
  'ff-report-templates': './skills/ff-report-templates/SKILL.md',
8
11
  };
9
- export const AGENT_PATHS = {
12
+ exports.AGENT_PATHS = {
10
13
  'ff-acceptance': './agents/ff-acceptance.md',
11
14
  'ff-review': './agents/ff-review.md',
12
15
  'ff-security': './agents/ff-security.md',
@@ -56,7 +59,7 @@ function resolveRootDir(input) {
56
59
  * - On failure: passes full CI output to LLM for fix instructions
57
60
  * - If management/ci.sh does not exist, quality gate does not run
58
61
  */
59
- export const StopQualityGatePlugin = async (input) => {
62
+ const StopQualityGatePlugin = async (input) => {
60
63
  const { worktree, directory, client } = input;
61
64
  const rootDir = resolveRootDir({ worktree, directory });
62
65
  // Skip quality gate if no valid directory (e.g., global config with no project)
@@ -66,7 +69,7 @@ export const StopQualityGatePlugin = async (input) => {
66
69
  // Create quality gate hooks
67
70
  let qualityGateHooks = {};
68
71
  try {
69
- qualityGateHooks = await createQualityGateHooks(input);
72
+ qualityGateHooks = await (0, stop_quality_gate_1.createQualityGateHooks)(input);
70
73
  }
71
74
  catch (error) {
72
75
  await log(client, 'error', 'quality-gate.init-error', {
@@ -77,5 +80,6 @@ export const StopQualityGatePlugin = async (input) => {
77
80
  ...qualityGateHooks,
78
81
  };
79
82
  };
83
+ exports.StopQualityGatePlugin = StopQualityGatePlugin;
80
84
  // Default export for OpenCode plugin discovery
81
- export default StopQualityGatePlugin;
85
+ exports.default = 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
- export function extractErrorLines(output, maxLines) {
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
- export function tailLines(output, maxLines) {
44
+ function tailLines(output, maxLines) {
41
45
  const lines = output.split('\n');
42
46
  if (lines.length <= maxLines) {
43
47
  return output;
@@ -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
- import { extractErrorLines, tailLines } from './output';
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
- export const DEFAULT_QUALITY_GATE = {
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
- export async function readJsonFile($, path) {
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
- export async function fileExists($, path) {
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
- export function mergeQualityGateConfig(root, dotOpencode) {
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
- export async function loadQualityGateConfig($, directory) {
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
- export function hasConfiguredCommands(config) {
90
+ function hasConfiguredCommands(config) {
83
91
  return !!(config.lint || config.build || config.test);
84
92
  }
@@ -1,3 +1,4 @@
1
+ "use strict";
1
2
  /**
2
3
  * Unit tests for quality-gate-config module
3
4
  *
@@ -6,21 +7,22 @@
6
7
  * - hasConfiguredCommands: checks if explicit commands are configured
7
8
  * - DEFAULT_QUALITY_GATE: verify default values
8
9
  */
9
- import { mergeQualityGateConfig, hasConfiguredCommands, DEFAULT_QUALITY_GATE, } from './quality-gate-config';
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ const quality_gate_config_1 = require("./quality-gate-config");
10
12
  describe('DEFAULT_QUALITY_GATE', () => {
11
13
  it('should have correct default values', () => {
12
- expect(DEFAULT_QUALITY_GATE.steps).toEqual(['lint', 'build', 'test']);
13
- expect(DEFAULT_QUALITY_GATE.useCiSh).toBe('auto');
14
- expect(DEFAULT_QUALITY_GATE.packageManager).toBe('auto');
15
- expect(DEFAULT_QUALITY_GATE.cacheSeconds).toBe(30);
16
- expect(DEFAULT_QUALITY_GATE.maxOutputLines).toBe(160);
17
- expect(DEFAULT_QUALITY_GATE.maxErrorLines).toBe(60);
18
- expect(DEFAULT_QUALITY_GATE.include.rustClippy).toBe(true);
14
+ expect(quality_gate_config_1.DEFAULT_QUALITY_GATE.steps).toEqual(['lint', 'build', 'test']);
15
+ expect(quality_gate_config_1.DEFAULT_QUALITY_GATE.useCiSh).toBe('auto');
16
+ expect(quality_gate_config_1.DEFAULT_QUALITY_GATE.packageManager).toBe('auto');
17
+ expect(quality_gate_config_1.DEFAULT_QUALITY_GATE.cacheSeconds).toBe(30);
18
+ expect(quality_gate_config_1.DEFAULT_QUALITY_GATE.maxOutputLines).toBe(160);
19
+ expect(quality_gate_config_1.DEFAULT_QUALITY_GATE.maxErrorLines).toBe(60);
20
+ expect(quality_gate_config_1.DEFAULT_QUALITY_GATE.include.rustClippy).toBe(true);
19
21
  });
20
22
  });
21
23
  describe('mergeQualityGateConfig', () => {
22
24
  it('should return empty config when both inputs are undefined', () => {
23
- const result = mergeQualityGateConfig(undefined, undefined);
25
+ const result = (0, quality_gate_config_1.mergeQualityGateConfig)(undefined, undefined);
24
26
  expect(result).toEqual({ include: {} });
25
27
  });
26
28
  it('should return root config when dotOpencode is undefined', () => {
@@ -28,7 +30,7 @@ describe('mergeQualityGateConfig', () => {
28
30
  lint: 'pnpm lint',
29
31
  cacheSeconds: 60,
30
32
  };
31
- const result = mergeQualityGateConfig(root, undefined);
33
+ const result = (0, quality_gate_config_1.mergeQualityGateConfig)(root, undefined);
32
34
  expect(result).toEqual({ lint: 'pnpm lint', cacheSeconds: 60, include: {} });
33
35
  });
34
36
  it('should return dotOpencode config when root is undefined', () => {
@@ -36,7 +38,7 @@ describe('mergeQualityGateConfig', () => {
36
38
  build: 'npm run build',
37
39
  maxOutputLines: 200,
38
40
  };
39
- const result = mergeQualityGateConfig(undefined, dotOpencode);
41
+ const result = (0, quality_gate_config_1.mergeQualityGateConfig)(undefined, dotOpencode);
40
42
  expect(result).toEqual({ build: 'npm run build', maxOutputLines: 200, include: {} });
41
43
  });
42
44
  it('should override root values with dotOpencode values', () => {
@@ -49,7 +51,7 @@ describe('mergeQualityGateConfig', () => {
49
51
  lint: 'npm run lint:strict',
50
52
  cacheSeconds: 60,
51
53
  };
52
- const result = mergeQualityGateConfig(root, dotOpencode);
54
+ const result = (0, quality_gate_config_1.mergeQualityGateConfig)(root, dotOpencode);
53
55
  expect(result.lint).toBe('npm run lint:strict');
54
56
  expect(result.build).toBe('pnpm build');
55
57
  expect(result.cacheSeconds).toBe(60);
@@ -65,7 +67,7 @@ describe('mergeQualityGateConfig', () => {
65
67
  rustClippy: true,
66
68
  },
67
69
  };
68
- const result = mergeQualityGateConfig(root, dotOpencode);
70
+ const result = (0, quality_gate_config_1.mergeQualityGateConfig)(root, dotOpencode);
69
71
  expect(result.include?.rustClippy).toBe(true);
70
72
  });
71
73
  it('should preserve root include values not overridden by dotOpencode', () => {
@@ -78,7 +80,7 @@ describe('mergeQualityGateConfig', () => {
78
80
  lint: 'custom lint',
79
81
  // No include specified
80
82
  };
81
- const result = mergeQualityGateConfig(root, dotOpencode);
83
+ const result = (0, quality_gate_config_1.mergeQualityGateConfig)(root, dotOpencode);
82
84
  expect(result.include?.rustClippy).toBe(true);
83
85
  expect(result.lint).toBe('custom lint');
84
86
  });
@@ -102,7 +104,7 @@ describe('mergeQualityGateConfig', () => {
102
104
  useCiSh: 'never',
103
105
  include: { rustClippy: true },
104
106
  };
105
- const result = mergeQualityGateConfig(root, dotOpencode);
107
+ const result = (0, quality_gate_config_1.mergeQualityGateConfig)(root, dotOpencode);
106
108
  expect(result.lint).toBe('lint-override');
107
109
  expect(result.build).toBe('build-root');
108
110
  expect(result.test).toBe('test-root');
@@ -118,7 +120,7 @@ describe('mergeQualityGateConfig', () => {
118
120
  });
119
121
  describe('hasConfiguredCommands', () => {
120
122
  it('should return false for empty config', () => {
121
- expect(hasConfiguredCommands({})).toBe(false);
123
+ expect((0, quality_gate_config_1.hasConfiguredCommands)({})).toBe(false);
122
124
  });
123
125
  it('should return false when only non-command properties are set', () => {
124
126
  const config = {
@@ -127,25 +129,25 @@ describe('hasConfiguredCommands', () => {
127
129
  useCiSh: 'always',
128
130
  packageManager: 'pnpm',
129
131
  };
130
- expect(hasConfiguredCommands(config)).toBe(false);
132
+ expect((0, quality_gate_config_1.hasConfiguredCommands)(config)).toBe(false);
131
133
  });
132
134
  it('should return true when lint is configured', () => {
133
135
  const config = {
134
136
  lint: 'pnpm lint',
135
137
  };
136
- expect(hasConfiguredCommands(config)).toBe(true);
138
+ expect((0, quality_gate_config_1.hasConfiguredCommands)(config)).toBe(true);
137
139
  });
138
140
  it('should return true when build is configured', () => {
139
141
  const config = {
140
142
  build: 'pnpm build',
141
143
  };
142
- expect(hasConfiguredCommands(config)).toBe(true);
144
+ expect((0, quality_gate_config_1.hasConfiguredCommands)(config)).toBe(true);
143
145
  });
144
146
  it('should return true when test is configured', () => {
145
147
  const config = {
146
148
  test: 'pnpm test',
147
149
  };
148
- expect(hasConfiguredCommands(config)).toBe(true);
150
+ expect((0, quality_gate_config_1.hasConfiguredCommands)(config)).toBe(true);
149
151
  });
150
152
  it('should return true when multiple commands are configured', () => {
151
153
  const config = {
@@ -153,12 +155,12 @@ describe('hasConfiguredCommands', () => {
153
155
  build: 'pnpm build',
154
156
  test: 'pnpm test',
155
157
  };
156
- expect(hasConfiguredCommands(config)).toBe(true);
158
+ expect((0, quality_gate_config_1.hasConfiguredCommands)(config)).toBe(true);
157
159
  });
158
160
  it('should return false for empty string commands', () => {
159
161
  const config = {
160
162
  lint: '',
161
163
  };
162
- expect(hasConfiguredCommands(config)).toBe(false);
164
+ expect((0, quality_gate_config_1.hasConfiguredCommands)(config)).toBe(false);
163
165
  });
164
166
  });
@@ -1,4 +1,4 @@
1
- import type { PluginInput, Hooks } from '@opencode-ai/plugin';
1
+ import type { PluginInput, Hooks } from '@opencode-ai/plugin' with { 'resolution-mode': 'import' };
2
2
  export declare const SECRET_PATTERNS: Array<{
3
3
  pattern: RegExp;
4
4
  replacement: string;
@@ -1,9 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SECRET_PATTERNS = void 0;
4
+ exports.sanitizeOutput = sanitizeOutput;
5
+ exports.truncateOutput = truncateOutput;
6
+ exports.createQualityGateHooks = createQualityGateHooks;
1
7
  const SERVICE_NAME = 'feature-factory';
2
8
  const IDLE_DEBOUNCE_MS = 500;
3
9
  const CI_TIMEOUT_MS = 300000; // 5 minutes
4
10
  const SESSION_TTL_MS = 3600000; // 1 hour
5
11
  const CLEANUP_INTERVAL_MS = 600000; // 10 minutes
6
- export const SECRET_PATTERNS = [
12
+ exports.SECRET_PATTERNS = [
7
13
  // AWS Access Key IDs
8
14
  { pattern: /AKIA[0-9A-Z]{16}/g, replacement: '[REDACTED_AWS_KEY]' },
9
15
  // GitHub Personal Access Tokens (classic)
@@ -67,9 +73,9 @@ export const SECRET_PATTERNS = [
67
73
  * Sanitizes CI output by redacting common secret patterns before sending to the LLM.
68
74
  * This helps prevent accidental exposure of sensitive information in prompts.
69
75
  */
70
- export function sanitizeOutput(output) {
76
+ function sanitizeOutput(output) {
71
77
  let sanitized = output;
72
- for (const { pattern, replacement } of SECRET_PATTERNS) {
78
+ for (const { pattern, replacement } of exports.SECRET_PATTERNS) {
73
79
  sanitized = sanitized.replace(pattern, replacement);
74
80
  }
75
81
  return sanitized;
@@ -78,7 +84,7 @@ export function sanitizeOutput(output) {
78
84
  * Truncates CI output to the last N lines to reduce prompt size and focus on relevant errors.
79
85
  * Adds a header indicating truncation if the output was longer than the limit.
80
86
  */
81
- export function truncateOutput(output, maxLines = 20) {
87
+ function truncateOutput(output, maxLines = 20) {
82
88
  const lines = output.split('\n');
83
89
  if (lines.length <= maxLines) {
84
90
  return output;
@@ -154,7 +160,7 @@ async function log(client, level, message, extra) {
154
160
  return undefined;
155
161
  }
156
162
  }
157
- export async function createQualityGateHooks(input) {
163
+ async function createQualityGateHooks(input) {
158
164
  const { client, $, directory } = input;
159
165
  async function ciShExists() {
160
166
  try {
@@ -1,3 +1,4 @@
1
+ "use strict";
1
2
  /**
2
3
  * Unit tests for stop-quality-gate module
3
4
  *
@@ -5,7 +6,8 @@
5
6
  * - sanitizeOutput: redacts secrets from CI output
6
7
  * - isSessionReadOnly: determines if session has write permissions
7
8
  */
8
- import { sanitizeOutput, truncateOutput } from './stop-quality-gate';
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ const stop_quality_gate_1 = require("./stop-quality-gate");
9
11
  function isSessionReadOnly(permission) {
10
12
  if (!permission)
11
13
  return false;
@@ -15,178 +17,178 @@ describe('sanitizeOutput', () => {
15
17
  describe('AWS credentials', () => {
16
18
  it('should redact AWS Access Key IDs', () => {
17
19
  const input = 'Found credentials: AKIAIOSFODNN7EXAMPLE in config';
18
- const result = sanitizeOutput(input);
20
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
19
21
  expect(result).toBe('Found credentials: [REDACTED_AWS_KEY] in config');
20
22
  });
21
23
  it('should redact multiple AWS keys', () => {
22
24
  const input = 'Key1: AKIAIOSFODNN7EXAMPLE Key2: AKIAI44QH8DHBEXAMPLE';
23
- const result = sanitizeOutput(input);
25
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
24
26
  expect(result).toBe('Key1: [REDACTED_AWS_KEY] Key2: [REDACTED_AWS_KEY]');
25
27
  });
26
28
  it('should not redact partial AWS key patterns', () => {
27
29
  const input = 'AKIA123'; // Too short
28
- const result = sanitizeOutput(input);
30
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
29
31
  expect(result).toBe('AKIA123');
30
32
  });
31
33
  });
32
34
  describe('GitHub tokens', () => {
33
35
  it('should redact GitHub Personal Access Tokens (classic)', () => {
34
36
  const input = 'Using ghp_aBcDeFgHiJkLmNoPqRsTuVwXyZ0123456789';
35
- const result = sanitizeOutput(input);
37
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
36
38
  expect(result).toBe('Using [REDACTED_GH_TOKEN]');
37
39
  });
38
40
  it('should redact GitHub Personal Access Tokens (fine-grained)', () => {
39
41
  const input = 'Using github_pat_11ABCDEFG_abcdefghijklmnopqrstuvwxyz';
40
- const result = sanitizeOutput(input);
42
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
41
43
  expect(result).toBe('Using [REDACTED_GH_TOKEN]');
42
44
  });
43
45
  it('should redact GitHub OAuth tokens', () => {
44
46
  const input = 'oauth=gho_aBcDeFgHiJkLmNoPqRsTuVwXyZ0123456789';
45
- const result = sanitizeOutput(input);
47
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
46
48
  expect(result).toBe('oauth=[REDACTED_GH_TOKEN]');
47
49
  });
48
50
  it('should redact GitHub App user-to-server tokens', () => {
49
51
  const input = 'Using ghu_aBcDeFgHiJkLmNoPqRsTuVwXyZ0123456789';
50
- const result = sanitizeOutput(input);
52
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
51
53
  expect(result).toBe('Using [REDACTED_GH_TOKEN]');
52
54
  });
53
55
  it('should redact GitHub App server-to-server tokens', () => {
54
56
  const input = 'Using ghs_aBcDeFgHiJkLmNoPqRsTuVwXyZ0123456789';
55
- const result = sanitizeOutput(input);
57
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
56
58
  expect(result).toBe('Using [REDACTED_GH_TOKEN]');
57
59
  });
58
60
  it('should not redact partial GitHub token patterns', () => {
59
61
  const input = 'ghp_abc'; // Too short
60
- const result = sanitizeOutput(input);
62
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
61
63
  expect(result).toBe('ghp_abc');
62
64
  });
63
65
  });
64
66
  describe('GitLab tokens', () => {
65
67
  it('should redact GitLab Personal Access Tokens', () => {
66
68
  const input = 'Using glpat-abcdefghij1234567890';
67
- const result = sanitizeOutput(input);
69
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
68
70
  expect(result).toBe('Using [REDACTED_GITLAB_TOKEN]');
69
71
  });
70
72
  it('should redact GitLab tokens with hyphens', () => {
71
73
  const input = 'Using glpat-abc-def-ghi-jkl-mnop-qrs';
72
- const result = sanitizeOutput(input);
74
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
73
75
  expect(result).toBe('Using [REDACTED_GITLAB_TOKEN]');
74
76
  });
75
77
  it('should not redact partial GitLab token patterns', () => {
76
78
  const input = 'glpat-short'; // Too short
77
- const result = sanitizeOutput(input);
79
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
78
80
  expect(result).toBe('glpat-short');
79
81
  });
80
82
  });
81
83
  describe('npm tokens', () => {
82
84
  it('should redact npm tokens', () => {
83
85
  const input = 'Using npm_aBcDeFgHiJkLmNoPqRsTuVwXyZ0123456789';
84
- const result = sanitizeOutput(input);
86
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
85
87
  expect(result).toBe('Using [REDACTED_NPM_TOKEN]');
86
88
  });
87
89
  it('should not redact partial npm token patterns', () => {
88
90
  const input = 'npm_abc'; // Too short
89
- const result = sanitizeOutput(input);
91
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
90
92
  expect(result).toBe('npm_abc');
91
93
  });
92
94
  });
93
95
  describe('Bearer tokens', () => {
94
96
  it('should redact Bearer tokens', () => {
95
97
  const input = 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9';
96
- const result = sanitizeOutput(input);
98
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
97
99
  expect(result).toBe('Authorization: Bearer [REDACTED]');
98
100
  });
99
101
  it('should handle case-insensitive Bearer', () => {
100
102
  const input = 'bearer abc123-token.value';
101
- const result = sanitizeOutput(input);
103
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
102
104
  expect(result).toBe('Bearer [REDACTED]');
103
105
  });
104
106
  });
105
107
  describe('API keys', () => {
106
108
  it('should redact api_key assignments', () => {
107
109
  const input = 'api_key=sk-1234567890abcdef';
108
- const result = sanitizeOutput(input);
110
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
109
111
  expect(result).toBe('api_key=[REDACTED]');
110
112
  });
111
113
  it('should redact api-key with hyphen', () => {
112
114
  const input = 'api-key: my-secret-key';
113
- const result = sanitizeOutput(input);
115
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
114
116
  expect(result).toBe('api_key=[REDACTED]');
115
117
  });
116
118
  it('should redact apikey without separator', () => {
117
119
  const input = 'apikey=abc123';
118
- const result = sanitizeOutput(input);
120
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
119
121
  expect(result).toBe('api_key=[REDACTED]');
120
122
  });
121
123
  it('should redact quoted api keys', () => {
122
124
  const input = "api_key='secret-value'";
123
- const result = sanitizeOutput(input);
125
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
124
126
  expect(result).toBe('api_key=[REDACTED]');
125
127
  });
126
128
  });
127
129
  describe('tokens', () => {
128
130
  it('should redact token assignments with 8+ char values', () => {
129
131
  const input = 'token=ghp_xxxxxxxxxxxxxxxxxxxx';
130
- const result = sanitizeOutput(input);
132
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
131
133
  expect(result).toBe('token=[REDACTED]');
132
134
  });
133
135
  it('should redact tokens plural with 8+ char values', () => {
134
136
  const input = 'tokens: secret12345';
135
- const result = sanitizeOutput(input);
137
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
136
138
  expect(result).toBe('token=[REDACTED]');
137
139
  });
138
140
  it('should NOT redact token with short values (less than 8 chars)', () => {
139
141
  const input = 'token=abc123';
140
- const result = sanitizeOutput(input);
142
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
141
143
  expect(result).toBe('token=abc123');
142
144
  });
143
145
  it('should NOT redact phrases like "token count: 5" (value too short)', () => {
144
146
  const input = 'token count: 5';
145
- const result = sanitizeOutput(input);
147
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
146
148
  expect(result).toBe('token count: 5');
147
149
  });
148
150
  it('should NOT redact "token: abc" (value too short)', () => {
149
151
  const input = 'token: abc';
150
- const result = sanitizeOutput(input);
152
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
151
153
  expect(result).toBe('token: abc');
152
154
  });
153
155
  it('should redact actual token values that are 8+ chars', () => {
154
156
  const input = 'token=abcd1234efgh';
155
- const result = sanitizeOutput(input);
157
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
156
158
  expect(result).toBe('token=[REDACTED]');
157
159
  });
158
160
  it('should redact quoted tokens with 8+ char values', () => {
159
161
  const input = 'token="my-secret-token-value"';
160
- const result = sanitizeOutput(input);
162
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
161
163
  expect(result).toBe('token=[REDACTED]');
162
164
  });
163
165
  });
164
166
  describe('passwords', () => {
165
167
  it('should redact password assignments', () => {
166
168
  const input = 'password=super-secret-123!';
167
- const result = sanitizeOutput(input);
169
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
168
170
  expect(result).toBe('password=[REDACTED]');
169
171
  });
170
172
  it('should redact passwords plural', () => {
171
173
  const input = 'passwords: "admin123"';
172
- const result = sanitizeOutput(input);
174
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
173
175
  expect(result).toBe('password=[REDACTED]');
174
176
  });
175
177
  it('should handle special characters in passwords', () => {
176
178
  const input = 'password=P@$$w0rd!#%';
177
- const result = sanitizeOutput(input);
179
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
178
180
  expect(result).toBe('password=[REDACTED]');
179
181
  });
180
182
  });
181
183
  describe('generic secrets', () => {
182
184
  it('should redact secret assignments', () => {
183
185
  const input = 'secret=my-app-secret-key';
184
- const result = sanitizeOutput(input);
186
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
185
187
  expect(result).toBe('secret=[REDACTED]');
186
188
  });
187
189
  it('should redact secrets plural', () => {
188
190
  const input = 'secrets: "confidential-data"';
189
- const result = sanitizeOutput(input);
191
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
190
192
  expect(result).toBe('secret=[REDACTED]');
191
193
  });
192
194
  });
@@ -194,40 +196,40 @@ describe('sanitizeOutput', () => {
194
196
  it('should redact long base64-like strings', () => {
195
197
  const base64 = 'YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY3ODk=';
196
198
  const input = `Encoded value: ${base64}`;
197
- const result = sanitizeOutput(input);
199
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
198
200
  expect(result).toBe('Encoded value: [REDACTED_BASE64]');
199
201
  });
200
202
  it('should not redact short base64 strings', () => {
201
203
  const input = 'Short: abc123';
202
- const result = sanitizeOutput(input);
204
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
203
205
  expect(result).toBe('Short: abc123');
204
206
  });
205
207
  it('should redact base64 without padding', () => {
206
208
  const base64 = 'YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY3ODkw';
207
- const result = sanitizeOutput(base64);
209
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(base64);
208
210
  expect(result).toBe('[REDACTED_BASE64]');
209
211
  });
210
212
  it('should redact base64 strings up to 500 chars (ReDoS prevention)', () => {
211
213
  // Generate a 500-char base64 string
212
214
  const base64 = 'A'.repeat(500);
213
- const result = sanitizeOutput(base64);
215
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(base64);
214
216
  expect(result).toBe('[REDACTED_BASE64]');
215
217
  });
216
218
  it('should handle base64 strings over 500 chars by matching only first 500 (ReDoS prevention)', () => {
217
219
  // Generate a 501-char base64 string - only first 500 chars match
218
220
  const base64 = 'A'.repeat(501);
219
- const result = sanitizeOutput(base64);
221
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(base64);
220
222
  // Pattern matches the first 500 chars, leaving 1 char behind
221
223
  expect(result).toBe('[REDACTED_BASE64]A');
222
224
  });
223
225
  it('should handle base64 at exactly 40 chars (minimum threshold)', () => {
224
226
  const base64 = 'A'.repeat(40);
225
- const result = sanitizeOutput(base64);
227
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(base64);
226
228
  expect(result).toBe('[REDACTED_BASE64]');
227
229
  });
228
230
  it('should NOT redact base64 strings under 40 chars', () => {
229
231
  const base64 = 'A'.repeat(39);
230
- const result = sanitizeOutput(base64);
232
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(base64);
231
233
  expect(result).toBe(base64);
232
234
  });
233
235
  });
@@ -238,7 +240,7 @@ describe('sanitizeOutput', () => {
238
240
  Auth: Bearer my-jwt-token
239
241
  password=admin123
240
242
  `;
241
- const result = sanitizeOutput(input);
243
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
242
244
  expect(result).toContain('[REDACTED_AWS_KEY]');
243
245
  expect(result).toContain('Bearer [REDACTED]');
244
246
  expect(result).toContain('password=[REDACTED]');
@@ -252,28 +254,28 @@ describe('sanitizeOutput', () => {
252
254
  const input = `-----BEGIN RSA PRIVATE KEY-----
253
255
  MIIEowIBAAKCAQEA0Z3VS5JJcds3xfn/ygWyF8PbnGy
254
256
  -----END RSA PRIVATE KEY-----`;
255
- const result = sanitizeOutput(input);
257
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
256
258
  expect(result).toBe('[REDACTED_PRIVATE_KEY]');
257
259
  });
258
260
  it('should redact OpenSSH private keys', () => {
259
261
  const input = `-----BEGIN OPENSSH PRIVATE KEY-----
260
262
  b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAA
261
263
  -----END OPENSSH PRIVATE KEY-----`;
262
- const result = sanitizeOutput(input);
264
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
263
265
  expect(result).toBe('[REDACTED_PRIVATE_KEY]');
264
266
  });
265
267
  it('should redact EC private keys', () => {
266
268
  const input = `-----BEGIN EC PRIVATE KEY-----
267
269
  MHQCAQEEICg7E4NN6YPWoU6/FXa5ON6Pt6LKBfA8WL
268
270
  -----END EC PRIVATE KEY-----`;
269
- const result = sanitizeOutput(input);
271
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
270
272
  expect(result).toBe('[REDACTED_PRIVATE_KEY]');
271
273
  });
272
274
  it('should redact generic private keys', () => {
273
275
  const input = `-----BEGIN PRIVATE KEY-----
274
276
  MIIEvgIBADANBgkqhkiG9w0BAQEFAASC
275
277
  -----END PRIVATE KEY-----`;
276
- const result = sanitizeOutput(input);
278
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
277
279
  expect(result).toBe('[REDACTED_PRIVATE_KEY]');
278
280
  });
279
281
  it('should redact private keys embedded in output', () => {
@@ -282,7 +284,7 @@ MIIEvgIBADANBgkqhkiG9w0BAQEFAASC
282
284
  MIIEowIBAAKCAQEA0Z3VS5JJcds3xfn/ygWyF8PbnGy
283
285
  -----END RSA PRIVATE KEY-----
284
286
  Done loading.`;
285
- const result = sanitizeOutput(input);
287
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
286
288
  expect(result).toBe(`Loading configuration...
287
289
  [REDACTED_PRIVATE_KEY]
288
290
  Done loading.`);
@@ -291,49 +293,49 @@ Done loading.`);
291
293
  describe('GCP credentials', () => {
292
294
  it('should redact GCP API keys', () => {
293
295
  const input = 'Using GCP key: AIzaSyDaGmWKa4JsXZ-HjGw7ISLn_3namBGewQe';
294
- const result = sanitizeOutput(input);
296
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
295
297
  expect(result).toBe('Using GCP key: [REDACTED_GCP_KEY]');
296
298
  });
297
299
  it('should redact multiple GCP API keys', () => {
298
300
  const input = 'Key1: AIzaSyDaGmWKa4JsXZ-HjGw7ISLn_3namBGewQe Key2: AIzaSyB-1234567890abcdefghijklmnopqrstu';
299
- const result = sanitizeOutput(input);
301
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
300
302
  expect(result).toBe('Key1: [REDACTED_GCP_KEY] Key2: [REDACTED_GCP_KEY]');
301
303
  });
302
304
  it('should not redact partial GCP API key patterns', () => {
303
305
  const input = 'AIza123'; // Too short
304
- const result = sanitizeOutput(input);
306
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
305
307
  expect(result).toBe('AIza123');
306
308
  });
307
309
  it('should redact GCP OAuth tokens', () => {
308
310
  const input = 'Authorization: ya29.a0AfH6SMBx-example-token-value_123';
309
- const result = sanitizeOutput(input);
311
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
310
312
  expect(result).toBe('Authorization: [REDACTED_GCP_TOKEN]');
311
313
  });
312
314
  it('should redact GCP OAuth tokens with various characters', () => {
313
315
  const input = 'token=ya29.Gl-abc_XYZ-123';
314
- const result = sanitizeOutput(input);
316
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
315
317
  expect(result).toBe('token=[REDACTED_GCP_TOKEN]');
316
318
  });
317
319
  });
318
320
  describe('Slack tokens', () => {
319
321
  it('should redact Slack bot tokens', () => {
320
322
  const input = 'SLACK_BOT_TOKEN=xoxb-123456789012-1234567890123-AbCdEfGhIjKlMnOpQrStUvWx';
321
- const result = sanitizeOutput(input);
323
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
322
324
  expect(result).toBe('SLACK_BOT_TOKEN=[REDACTED_SLACK_TOKEN]');
323
325
  });
324
326
  it('should redact Slack user tokens', () => {
325
327
  const input = 'Using xoxp-123456789012-123456789012-123456789012-abcdef1234567890abcdef1234567890';
326
- const result = sanitizeOutput(input);
328
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
327
329
  expect(result).toBe('Using [REDACTED_SLACK_TOKEN]');
328
330
  });
329
331
  it('should redact Slack app tokens', () => {
330
332
  const input = 'APP_TOKEN=xapp-1-A0123BCDEFG-1234567890123-abcdefghijklmnopqrstuvwxyz0123456789';
331
- const result = sanitizeOutput(input);
333
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
332
334
  expect(result).toBe('APP_TOKEN=[REDACTED_SLACK_TOKEN]');
333
335
  });
334
336
  it('should redact Slack webhook URLs', () => {
335
337
  const input = 'Webhook: https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX';
336
- const result = sanitizeOutput(input);
338
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
337
339
  expect(result).toBe('Webhook: https://[REDACTED_SLACK_WEBHOOK]');
338
340
  });
339
341
  it('should redact multiple different Slack token types', () => {
@@ -342,7 +344,7 @@ Done loading.`);
342
344
  User: xoxp-789-012-def
343
345
  App: xapp-345-ghi
344
346
  `;
345
- const result = sanitizeOutput(input);
347
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
346
348
  expect(result).toContain('[REDACTED_SLACK_TOKEN]');
347
349
  expect(result).not.toContain('xoxb-');
348
350
  expect(result).not.toContain('xoxp-');
@@ -353,100 +355,100 @@ Done loading.`);
353
355
  it('should redact Stripe live secret keys', () => {
354
356
  // 24 chars after sk_live_: 51ABC123DEF456GHI789JKLM
355
357
  const input = 'STRIPE_SECRET_KEY=sk_live_51ABC123DEF456GHI789JKLM';
356
- const result = sanitizeOutput(input);
358
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
357
359
  expect(result).toBe('STRIPE_SECRET_KEY=[REDACTED_STRIPE_KEY]');
358
360
  });
359
361
  it('should redact Stripe test secret keys', () => {
360
362
  // 24 chars after sk_test_: 51ABC123DEF456GHI789JKLM
361
363
  const input = 'Using sk_test_51ABC123DEF456GHI789JKLM for testing';
362
- const result = sanitizeOutput(input);
364
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
363
365
  expect(result).toBe('Using [REDACTED_STRIPE_KEY] for testing');
364
366
  });
365
367
  it('should redact Stripe live restricted keys', () => {
366
368
  // 24 chars after rk_live_: 51ABC123DEF456GHI789JKLM
367
369
  const input = 'RESTRICTED_KEY=rk_live_51ABC123DEF456GHI789JKLM';
368
- const result = sanitizeOutput(input);
370
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
369
371
  expect(result).toBe('RESTRICTED_KEY=[REDACTED_STRIPE_KEY]');
370
372
  });
371
373
  it('should redact Stripe test restricted keys', () => {
372
374
  // 24 chars after rk_test_: 51ABC123DEF456GHI789JKLM
373
375
  const input = 'Using rk_test_51ABC123DEF456GHI789JKLM for testing';
374
- const result = sanitizeOutput(input);
376
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
375
377
  expect(result).toBe('Using [REDACTED_STRIPE_KEY] for testing');
376
378
  });
377
379
  it('should not redact Stripe keys that are too short', () => {
378
380
  const input = 'sk_live_short'; // Less than 24 characters after prefix
379
- const result = sanitizeOutput(input);
381
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
380
382
  expect(result).toBe('sk_live_short');
381
383
  });
382
384
  it('should redact multiple Stripe keys', () => {
383
385
  // 24 chars after each prefix
384
386
  const input = 'Live: sk_live_51ABC123DEF456GHI789JKLM Test: sk_test_51XYZ789ABC123DEF456GHIJ';
385
- const result = sanitizeOutput(input);
387
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
386
388
  expect(result).toBe('Live: [REDACTED_STRIPE_KEY] Test: [REDACTED_STRIPE_KEY]');
387
389
  });
388
390
  it('should redact long Stripe keys', () => {
389
391
  const input = 'sk_live_51ABC123DEF456GHI789JKLmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOP';
390
- const result = sanitizeOutput(input);
392
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
391
393
  expect(result).toBe('[REDACTED_STRIPE_KEY]');
392
394
  });
393
395
  });
394
396
  describe('database connection strings', () => {
395
397
  it('should redact PostgreSQL connection strings', () => {
396
398
  const input = 'DATABASE_URL=postgres://admin:secretpass123@db.example.com:5432/mydb';
397
- const result = sanitizeOutput(input);
399
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
398
400
  expect(result).toBe('DATABASE_URL=[REDACTED_CONNECTION_STRING]');
399
401
  });
400
402
  it('should redact MongoDB connection strings with +srv', () => {
401
403
  const input = 'Connecting to mongodb+srv://user:p@ssw0rd@cluster.mongodb.net/database';
402
- const result = sanitizeOutput(input);
404
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
403
405
  expect(result).toBe('Connecting to [REDACTED_CONNECTION_STRING]');
404
406
  });
405
407
  it('should redact Redis connection strings', () => {
406
408
  const input = 'REDIS_URL=redis://default:myredispassword@redis.example.com:6379';
407
- const result = sanitizeOutput(input);
409
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
408
410
  expect(result).toBe('REDIS_URL=[REDACTED_CONNECTION_STRING]');
409
411
  });
410
412
  it('should redact MySQL connection strings', () => {
411
413
  const input = 'mysql://root:rootpassword@localhost:3306/testdb';
412
- const result = sanitizeOutput(input);
414
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
413
415
  expect(result).toBe('[REDACTED_CONNECTION_STRING]');
414
416
  });
415
417
  it('should redact rediss (TLS) connection strings', () => {
416
418
  const input = 'rediss://user:password@secure-redis.example.com:6380';
417
- const result = sanitizeOutput(input);
419
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
418
420
  expect(result).toBe('[REDACTED_CONNECTION_STRING]');
419
421
  });
420
422
  it('should redact connection strings with URL-encoded passwords', () => {
421
423
  const input = 'mongodb://user:p%40ss%23word@host/db';
422
- const result = sanitizeOutput(input);
424
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
423
425
  expect(result).toBe('[REDACTED_CONNECTION_STRING]');
424
426
  });
425
427
  it('should redact PostgreSQL with URL-encoded @ in password', () => {
426
428
  const input = 'postgres://admin:secret%40pass@db.example.com:5432/mydb';
427
- const result = sanitizeOutput(input);
429
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
428
430
  expect(result).toBe('[REDACTED_CONNECTION_STRING]');
429
431
  });
430
432
  it('should redact connection strings with multiple URL-encoded characters', () => {
431
433
  const input = 'mysql://root:p%40ss%3Dw%26rd%21@localhost:3306/testdb';
432
- const result = sanitizeOutput(input);
434
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
433
435
  expect(result).toBe('[REDACTED_CONNECTION_STRING]');
434
436
  });
435
437
  it('should redact MongoDB+srv with URL-encoded password', () => {
436
438
  const input = 'mongodb+srv://user:my%40complex%23pass@cluster.mongodb.net/database';
437
- const result = sanitizeOutput(input);
439
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
438
440
  expect(result).toBe('[REDACTED_CONNECTION_STRING]');
439
441
  });
440
442
  });
441
443
  describe('non-secret content', () => {
442
444
  it('should preserve normal log output', () => {
443
445
  const input = 'Build completed successfully in 2.5s';
444
- const result = sanitizeOutput(input);
446
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
445
447
  expect(result).toBe(input);
446
448
  });
447
449
  it('should preserve error messages without secrets', () => {
448
450
  const input = 'Error: Cannot find module "./missing-file"';
449
- const result = sanitizeOutput(input);
451
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
450
452
  expect(result).toBe(input);
451
453
  });
452
454
  it('should preserve stack traces', () => {
@@ -455,7 +457,7 @@ Done loading.`);
455
457
  at Object.<anonymous> (/app/test.js:10:5)
456
458
  at Module._compile (internal/modules/cjs/loader.js:1085:14)
457
459
  `;
458
- const result = sanitizeOutput(input);
460
+ const result = (0, stop_quality_gate_1.sanitizeOutput)(input);
459
461
  expect(result).toBe(input);
460
462
  });
461
463
  });
@@ -506,17 +508,17 @@ describe('isSessionReadOnly', () => {
506
508
  describe('truncateOutput', () => {
507
509
  it('should return output unchanged if under maxLines', () => {
508
510
  const input = 'line1\nline2\nline3';
509
- expect(truncateOutput(input, 20)).toBe(input);
511
+ expect((0, stop_quality_gate_1.truncateOutput)(input, 20)).toBe(input);
510
512
  });
511
513
  it('should return output unchanged if exactly at maxLines', () => {
512
514
  const lines = Array.from({ length: 20 }, (_, i) => `line${i + 1}`);
513
515
  const input = lines.join('\n');
514
- expect(truncateOutput(input, 20)).toBe(input);
516
+ expect((0, stop_quality_gate_1.truncateOutput)(input, 20)).toBe(input);
515
517
  });
516
518
  it('should truncate to last 20 lines by default', () => {
517
519
  const lines = Array.from({ length: 30 }, (_, i) => `line${i + 1}`);
518
520
  const input = lines.join('\n');
519
- const result = truncateOutput(input);
521
+ const result = (0, stop_quality_gate_1.truncateOutput)(input);
520
522
  expect(result).toContain('... (10 lines omitted)');
521
523
  expect(result).toContain('line11');
522
524
  expect(result).toContain('line30');
@@ -526,7 +528,7 @@ describe('truncateOutput', () => {
526
528
  it('should truncate to custom maxLines', () => {
527
529
  const lines = Array.from({ length: 15 }, (_, i) => `line${i + 1}`);
528
530
  const input = lines.join('\n');
529
- const result = truncateOutput(input, 5);
531
+ const result = (0, stop_quality_gate_1.truncateOutput)(input, 5);
530
532
  expect(result).toContain('... (10 lines omitted)');
531
533
  expect(result).toContain('line11');
532
534
  expect(result).toContain('line15');
@@ -534,15 +536,15 @@ describe('truncateOutput', () => {
534
536
  });
535
537
  it('should handle single line output', () => {
536
538
  const input = 'single line';
537
- expect(truncateOutput(input, 20)).toBe(input);
539
+ expect((0, stop_quality_gate_1.truncateOutput)(input, 20)).toBe(input);
538
540
  });
539
541
  it('should handle empty output', () => {
540
- expect(truncateOutput('', 20)).toBe('');
542
+ expect((0, stop_quality_gate_1.truncateOutput)('', 20)).toBe('');
541
543
  });
542
544
  it('should preserve line content exactly', () => {
543
545
  const lines = Array.from({ length: 25 }, (_, i) => `Error at line ${i + 1}: something failed`);
544
546
  const input = lines.join('\n');
545
- const result = truncateOutput(input, 10);
547
+ const result = (0, stop_quality_gate_1.truncateOutput)(input, 10);
546
548
  expect(result).toContain('Error at line 16: something failed');
547
549
  expect(result).toContain('Error at line 25: something failed');
548
550
  });
package/dist/types.js CHANGED
@@ -1 +1,2 @@
1
- export {};
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json CHANGED
@@ -1,23 +1,14 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "@syntesseraai/opencode-feature-factory",
4
- "version": "0.2.7",
4
+ "version": "0.2.9",
5
5
  "description": "OpenCode plugin for Feature Factory agents - provides sub-agents and skills for validation, review, security, and architecture assessment",
6
- "type": "module",
7
6
  "license": "MIT",
8
7
  "main": "./dist/index.js",
9
- "module": "./dist/index.js",
10
8
  "types": "./dist/index.d.ts",
11
9
  "bin": {
12
10
  "ff-deploy": "./bin/ff-deploy.js"
13
11
  },
14
- "exports": {
15
- ".": {
16
- "types": "./dist/index.d.ts",
17
- "import": "./dist/index.js",
18
- "default": "./dist/index.js"
19
- }
20
- },
21
12
  "files": [
22
13
  "dist",
23
14
  "assets",
@@ -40,7 +31,7 @@
40
31
  ],
41
32
  "scripts": {},
42
33
  "dependencies": {
43
- "@opencode-ai/plugin": "1.1.48"
34
+ "@opencode-ai/plugin": "1.1.36"
44
35
  },
45
36
  "devDependencies": {
46
37
  "@types/bun": "^1.2.6",