@link-assistant/hive-mind 0.39.0
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/CHANGELOG.md +20 -0
- package/LICENSE +24 -0
- package/README.md +769 -0
- package/package.json +58 -0
- package/src/agent.lib.mjs +705 -0
- package/src/agent.prompts.lib.mjs +196 -0
- package/src/buildUserMention.lib.mjs +71 -0
- package/src/claude-limits.lib.mjs +389 -0
- package/src/claude.lib.mjs +1445 -0
- package/src/claude.prompts.lib.mjs +203 -0
- package/src/codex.lib.mjs +552 -0
- package/src/codex.prompts.lib.mjs +194 -0
- package/src/config.lib.mjs +207 -0
- package/src/contributing-guidelines.lib.mjs +268 -0
- package/src/exit-handler.lib.mjs +205 -0
- package/src/git.lib.mjs +145 -0
- package/src/github-issue-creator.lib.mjs +246 -0
- package/src/github-linking.lib.mjs +152 -0
- package/src/github.batch.lib.mjs +272 -0
- package/src/github.graphql.lib.mjs +258 -0
- package/src/github.lib.mjs +1479 -0
- package/src/hive.config.lib.mjs +254 -0
- package/src/hive.mjs +1500 -0
- package/src/instrument.mjs +191 -0
- package/src/interactive-mode.lib.mjs +1000 -0
- package/src/lenv-reader.lib.mjs +206 -0
- package/src/lib.mjs +490 -0
- package/src/lino.lib.mjs +176 -0
- package/src/local-ci-checks.lib.mjs +324 -0
- package/src/memory-check.mjs +419 -0
- package/src/model-mapping.lib.mjs +145 -0
- package/src/model-validation.lib.mjs +278 -0
- package/src/opencode.lib.mjs +479 -0
- package/src/opencode.prompts.lib.mjs +194 -0
- package/src/protect-branch.mjs +159 -0
- package/src/review.mjs +433 -0
- package/src/reviewers-hive.mjs +643 -0
- package/src/sentry.lib.mjs +284 -0
- package/src/solve.auto-continue.lib.mjs +568 -0
- package/src/solve.auto-pr.lib.mjs +1374 -0
- package/src/solve.branch-errors.lib.mjs +341 -0
- package/src/solve.branch.lib.mjs +230 -0
- package/src/solve.config.lib.mjs +342 -0
- package/src/solve.error-handlers.lib.mjs +256 -0
- package/src/solve.execution.lib.mjs +291 -0
- package/src/solve.feedback.lib.mjs +436 -0
- package/src/solve.mjs +1128 -0
- package/src/solve.preparation.lib.mjs +210 -0
- package/src/solve.repo-setup.lib.mjs +114 -0
- package/src/solve.repository.lib.mjs +961 -0
- package/src/solve.results.lib.mjs +558 -0
- package/src/solve.session.lib.mjs +135 -0
- package/src/solve.validation.lib.mjs +325 -0
- package/src/solve.watch.lib.mjs +572 -0
- package/src/start-screen.mjs +324 -0
- package/src/task.mjs +308 -0
- package/src/telegram-bot.mjs +1481 -0
- package/src/telegram-markdown.lib.mjs +64 -0
- package/src/usage-limit.lib.mjs +218 -0
- package/src/version.lib.mjs +41 -0
- package/src/youtrack/solve.youtrack.lib.mjs +116 -0
- package/src/youtrack/youtrack-sync.mjs +219 -0
- package/src/youtrack/youtrack.lib.mjs +425 -0
package/src/lino.lib.mjs
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
if (typeof use === 'undefined') {
|
|
2
|
+
globalThis.use = (await eval(await (await fetch('https://unpkg.com/use-m/use.js')).text())).use;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
const linoModule = await use('links-notation');
|
|
6
|
+
const LinoParser = linoModule.Parser || linoModule.default?.Parser;
|
|
7
|
+
|
|
8
|
+
const fs = await import('fs');
|
|
9
|
+
const path = await import('path');
|
|
10
|
+
const os = await import('os');
|
|
11
|
+
|
|
12
|
+
export class LinksNotationManager {
|
|
13
|
+
constructor() {
|
|
14
|
+
this.parser = new LinoParser();
|
|
15
|
+
this.cacheDir = path.join(os.homedir(), '.hive-mind');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
parse(input) {
|
|
19
|
+
if (!input) return [];
|
|
20
|
+
|
|
21
|
+
const parsed = this.parser.parse(input);
|
|
22
|
+
|
|
23
|
+
if (parsed && parsed.length > 0) {
|
|
24
|
+
const link = parsed[0];
|
|
25
|
+
const values = [];
|
|
26
|
+
|
|
27
|
+
if (link.values && link.values.length > 0) {
|
|
28
|
+
for (const value of link.values) {
|
|
29
|
+
const val = value.id || value;
|
|
30
|
+
values.push(val);
|
|
31
|
+
}
|
|
32
|
+
} else if (link.id) {
|
|
33
|
+
values.push(link.id);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return values;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
parseNumericIds(input) {
|
|
43
|
+
if (!input) return [];
|
|
44
|
+
|
|
45
|
+
const parsed = this.parser.parse(input);
|
|
46
|
+
|
|
47
|
+
if (parsed && parsed.length > 0) {
|
|
48
|
+
const link = parsed[0];
|
|
49
|
+
const ids = [];
|
|
50
|
+
|
|
51
|
+
if (link.values && link.values.length > 0) {
|
|
52
|
+
for (const value of link.values) {
|
|
53
|
+
const num = parseInt(value.id || value);
|
|
54
|
+
if (!isNaN(num)) {
|
|
55
|
+
ids.push(num);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
} else if (link.id) {
|
|
59
|
+
const nums = link.id.match(/\d+/g);
|
|
60
|
+
if (nums) {
|
|
61
|
+
ids.push(...nums.map(n => parseInt(n)).filter(n => !isNaN(n)));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return ids;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
parseStringValues(input) {
|
|
72
|
+
if (!input) return [];
|
|
73
|
+
|
|
74
|
+
const parsed = this.parser.parse(input);
|
|
75
|
+
|
|
76
|
+
if (parsed && parsed.length > 0) {
|
|
77
|
+
const link = parsed[0];
|
|
78
|
+
const links = [];
|
|
79
|
+
|
|
80
|
+
if (link.values && link.values.length > 0) {
|
|
81
|
+
for (const value of link.values) {
|
|
82
|
+
const linkStr = value.id || value;
|
|
83
|
+
if (typeof linkStr === 'string') {
|
|
84
|
+
links.push(linkStr);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
} else if (link.id) {
|
|
88
|
+
if (typeof link.id === 'string') {
|
|
89
|
+
links.push(link.id);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return links;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return [];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
format(values) {
|
|
100
|
+
if (!values || values.length === 0) return '()';
|
|
101
|
+
|
|
102
|
+
const formattedValues = values.map(value => ` ${value}`).join('\n');
|
|
103
|
+
return `(\n${formattedValues}\n)`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async ensureCacheDir() {
|
|
107
|
+
try {
|
|
108
|
+
await fs.promises.access(this.cacheDir);
|
|
109
|
+
return false;
|
|
110
|
+
} catch {
|
|
111
|
+
await fs.promises.mkdir(this.cacheDir, { recursive: true });
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async saveToCache(filename, values) {
|
|
117
|
+
await this.ensureCacheDir();
|
|
118
|
+
const cacheFile = path.join(this.cacheDir, filename);
|
|
119
|
+
const linksNotation = this.format(values);
|
|
120
|
+
await fs.promises.writeFile(cacheFile, linksNotation);
|
|
121
|
+
return cacheFile;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async loadFromCache(filename) {
|
|
125
|
+
const cacheFile = path.join(this.cacheDir, filename);
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
await fs.promises.access(cacheFile);
|
|
129
|
+
} catch {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const content = await fs.promises.readFile(cacheFile, 'utf8');
|
|
134
|
+
return {
|
|
135
|
+
raw: content,
|
|
136
|
+
parsed: this.parse(content),
|
|
137
|
+
numericIds: this.parseNumericIds(content),
|
|
138
|
+
stringValues: this.parseStringValues(content),
|
|
139
|
+
file: cacheFile
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async cacheExists(filename) {
|
|
144
|
+
const cacheFile = path.join(this.cacheDir, filename);
|
|
145
|
+
try {
|
|
146
|
+
await fs.promises.access(cacheFile);
|
|
147
|
+
return true;
|
|
148
|
+
} catch {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
getCachePath(filename) {
|
|
154
|
+
return path.join(this.cacheDir, filename);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async requireCache(filename, errorMessage) {
|
|
158
|
+
const cache = await this.loadFromCache(filename);
|
|
159
|
+
|
|
160
|
+
if (!cache) {
|
|
161
|
+
const cacheFile = this.getCachePath(filename);
|
|
162
|
+
console.error(`❌ ${errorMessage || `Cache file not found: ${cacheFile}`}`);
|
|
163
|
+
console.log('💡 Run the appropriate script first to create the cache file');
|
|
164
|
+
process.exit(1);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
console.log(`📂 Using cached data from: ${cache.file}\n`);
|
|
168
|
+
return cache;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export const CACHE_FILES = {
|
|
173
|
+
TELEGRAM_CHATS: 'telegram-chats.lino'
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
export const lino = new LinksNotationManager();
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Local CI Checks Module
|
|
5
|
+
* Detects and runs local CI checks before pushing to avoid CI failures
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
if (typeof globalThis.use === 'undefined') {
|
|
9
|
+
globalThis.use = (await eval(await (await fetch('https://unpkg.com/use-m/use.js')).text())).use;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const { $ } = await use('command-stream');
|
|
13
|
+
const fs = (await use('fs')).promises;
|
|
14
|
+
const path = (await use('path')).default;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Detect which CI tools are configured in the project
|
|
18
|
+
* @param {string} workDir - Working directory
|
|
19
|
+
* @returns {Promise<Object>} Detected CI tools and their configs
|
|
20
|
+
*/
|
|
21
|
+
export async function detectCITools(workDir) {
|
|
22
|
+
const tools = {
|
|
23
|
+
python: {
|
|
24
|
+
ruff: false,
|
|
25
|
+
mypy: false,
|
|
26
|
+
pytest: false,
|
|
27
|
+
nox: false,
|
|
28
|
+
black: false,
|
|
29
|
+
flake8: false
|
|
30
|
+
},
|
|
31
|
+
javascript: {
|
|
32
|
+
eslint: false,
|
|
33
|
+
prettier: false,
|
|
34
|
+
jest: false,
|
|
35
|
+
vitest: false
|
|
36
|
+
},
|
|
37
|
+
rust: {
|
|
38
|
+
rustfmt: false,
|
|
39
|
+
clippy: false,
|
|
40
|
+
cargoTest: false
|
|
41
|
+
},
|
|
42
|
+
general: {
|
|
43
|
+
preCommit: false
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
// Check for Python tools
|
|
49
|
+
const pyprojectPath = path.join(workDir, 'pyproject.toml');
|
|
50
|
+
const ruffConfigPath = path.join(workDir, 'ruff.toml');
|
|
51
|
+
const mypyConfigPath = path.join(workDir, 'mypy.ini');
|
|
52
|
+
const noxfilePath = path.join(workDir, 'noxfile.py');
|
|
53
|
+
const flake8ConfigPath = path.join(workDir, '.flake8');
|
|
54
|
+
|
|
55
|
+
// Check ruff
|
|
56
|
+
try {
|
|
57
|
+
await fs.access(ruffConfigPath);
|
|
58
|
+
tools.python.ruff = true;
|
|
59
|
+
} catch {
|
|
60
|
+
// File doesn't exist, ruff not configured
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
const pyproject = await fs.readFile(pyprojectPath, 'utf-8');
|
|
65
|
+
if (pyproject.includes('[tool.ruff]')) tools.python.ruff = true;
|
|
66
|
+
if (pyproject.includes('[tool.mypy]')) tools.python.mypy = true;
|
|
67
|
+
if (pyproject.includes('[tool.black]')) tools.python.black = true;
|
|
68
|
+
if (pyproject.includes('[tool.pytest]')) tools.python.pytest = true;
|
|
69
|
+
} catch {
|
|
70
|
+
// File doesn't exist or can't be read, tools not configured in pyproject.toml
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Check mypy
|
|
74
|
+
try {
|
|
75
|
+
await fs.access(mypyConfigPath);
|
|
76
|
+
tools.python.mypy = true;
|
|
77
|
+
} catch {
|
|
78
|
+
// File doesn't exist, mypy not configured
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Check nox
|
|
82
|
+
try {
|
|
83
|
+
await fs.access(noxfilePath);
|
|
84
|
+
tools.python.nox = true;
|
|
85
|
+
} catch {
|
|
86
|
+
// File doesn't exist, nox not configured
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Check flake8
|
|
90
|
+
try {
|
|
91
|
+
await fs.access(flake8ConfigPath);
|
|
92
|
+
tools.python.flake8 = true;
|
|
93
|
+
} catch {
|
|
94
|
+
// File doesn't exist, flake8 not configured
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Check for JavaScript tools
|
|
98
|
+
const packageJsonPath = path.join(workDir, 'package.json');
|
|
99
|
+
try {
|
|
100
|
+
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf-8'));
|
|
101
|
+
|
|
102
|
+
if (packageJson.devDependencies?.eslint || packageJson.dependencies?.eslint) {
|
|
103
|
+
tools.javascript.eslint = true;
|
|
104
|
+
}
|
|
105
|
+
if (packageJson.devDependencies?.prettier || packageJson.dependencies?.prettier) {
|
|
106
|
+
tools.javascript.prettier = true;
|
|
107
|
+
}
|
|
108
|
+
if (packageJson.devDependencies?.jest || packageJson.dependencies?.jest) {
|
|
109
|
+
tools.javascript.jest = true;
|
|
110
|
+
}
|
|
111
|
+
if (packageJson.devDependencies?.vitest || packageJson.dependencies?.vitest) {
|
|
112
|
+
tools.javascript.vitest = true;
|
|
113
|
+
}
|
|
114
|
+
} catch {
|
|
115
|
+
// File doesn't exist or can't be parsed, JavaScript tools not configured
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Check for Rust tools
|
|
119
|
+
const cargoTomlPath = path.join(workDir, 'Cargo.toml');
|
|
120
|
+
try {
|
|
121
|
+
await fs.access(cargoTomlPath);
|
|
122
|
+
tools.rust.rustfmt = true;
|
|
123
|
+
tools.rust.clippy = true;
|
|
124
|
+
tools.rust.cargoTest = true;
|
|
125
|
+
} catch {
|
|
126
|
+
// File doesn't exist, Rust tools not configured
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Check for pre-commit
|
|
130
|
+
const preCommitPath = path.join(workDir, '.pre-commit-config.yaml');
|
|
131
|
+
try {
|
|
132
|
+
await fs.access(preCommitPath);
|
|
133
|
+
tools.general.preCommit = true;
|
|
134
|
+
} catch {
|
|
135
|
+
// File doesn't exist, pre-commit not configured
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
} catch (err) {
|
|
139
|
+
console.error('Error detecting CI tools:', err.message);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return tools;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Run local CI checks
|
|
147
|
+
* @param {string} workDir - Working directory
|
|
148
|
+
* @param {Object} tools - Detected CI tools from detectCITools()
|
|
149
|
+
* @param {Object} options - Options for running checks
|
|
150
|
+
* @returns {Promise<Object>} Results of CI checks
|
|
151
|
+
*/
|
|
152
|
+
export async function runLocalCIChecks(workDir, tools, options = {}) {
|
|
153
|
+
const results = {
|
|
154
|
+
success: true,
|
|
155
|
+
checks: [],
|
|
156
|
+
errors: []
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const verbose = options.verbose || false;
|
|
160
|
+
|
|
161
|
+
// Run Python checks
|
|
162
|
+
if (tools.python.ruff) {
|
|
163
|
+
const result = await runCheck(workDir, 'ruff check .', 'Ruff linting', verbose);
|
|
164
|
+
results.checks.push(result);
|
|
165
|
+
if (!result.success) results.success = false;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (tools.python.mypy) {
|
|
169
|
+
const result = await runCheck(workDir, 'mypy .', 'MyPy type checking', verbose);
|
|
170
|
+
results.checks.push(result);
|
|
171
|
+
if (!result.success) results.success = false;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (tools.python.black) {
|
|
175
|
+
const result = await runCheck(workDir, 'black --check .', 'Black formatting', verbose);
|
|
176
|
+
results.checks.push(result);
|
|
177
|
+
if (!result.success) results.success = false;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (tools.python.flake8) {
|
|
181
|
+
const result = await runCheck(workDir, 'flake8 .', 'Flake8 linting', verbose);
|
|
182
|
+
results.checks.push(result);
|
|
183
|
+
if (!result.success) results.success = false;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Run JavaScript checks
|
|
187
|
+
if (tools.javascript.eslint) {
|
|
188
|
+
const result = await runCheck(workDir, 'npm run lint', 'ESLint', verbose);
|
|
189
|
+
results.checks.push(result);
|
|
190
|
+
if (!result.success) results.success = false;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (tools.javascript.prettier) {
|
|
194
|
+
const result = await runCheck(workDir, 'npm run format:check', 'Prettier', verbose);
|
|
195
|
+
results.checks.push(result);
|
|
196
|
+
if (!result.success) results.success = false;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Run Rust checks
|
|
200
|
+
if (tools.rust.rustfmt) {
|
|
201
|
+
const result = await runCheck(workDir, 'cargo fmt -- --check', 'Rustfmt', verbose);
|
|
202
|
+
results.checks.push(result);
|
|
203
|
+
if (!result.success) results.success = false;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (tools.rust.clippy) {
|
|
207
|
+
const result = await runCheck(workDir, 'cargo clippy -- -D warnings', 'Clippy', verbose);
|
|
208
|
+
results.checks.push(result);
|
|
209
|
+
if (!result.success) results.success = false;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Run tests only if requested
|
|
213
|
+
if (options.runTests) {
|
|
214
|
+
if (tools.python.pytest) {
|
|
215
|
+
const result = await runCheck(workDir, 'pytest', 'Pytest', verbose);
|
|
216
|
+
results.checks.push(result);
|
|
217
|
+
if (!result.success) results.success = false;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (tools.python.nox) {
|
|
221
|
+
const result = await runCheck(workDir, 'nox', 'Nox', verbose);
|
|
222
|
+
results.checks.push(result);
|
|
223
|
+
if (!result.success) results.success = false;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (tools.javascript.jest) {
|
|
227
|
+
const result = await runCheck(workDir, 'npm test', 'Jest', verbose);
|
|
228
|
+
results.checks.push(result);
|
|
229
|
+
if (!result.success) results.success = false;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (tools.rust.cargoTest) {
|
|
233
|
+
const result = await runCheck(workDir, 'cargo test', 'Cargo test', verbose);
|
|
234
|
+
results.checks.push(result);
|
|
235
|
+
if (!result.success) results.success = false;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return results;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Run a single check command
|
|
244
|
+
* @param {string} workDir - Working directory
|
|
245
|
+
* @param {string} command - Command to run
|
|
246
|
+
* @param {string} name - Display name for the check
|
|
247
|
+
* @param {boolean} verbose - Whether to show verbose output
|
|
248
|
+
* @returns {Promise<Object>} Check result
|
|
249
|
+
*/
|
|
250
|
+
async function runCheck(workDir, command, name, verbose) {
|
|
251
|
+
const result = {
|
|
252
|
+
name,
|
|
253
|
+
command,
|
|
254
|
+
success: false,
|
|
255
|
+
output: '',
|
|
256
|
+
error: ''
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
try {
|
|
260
|
+
const checkResult = await $`cd ${workDir} && ${command}`.raw().trim();
|
|
261
|
+
|
|
262
|
+
result.success = checkResult.exitCode === 0;
|
|
263
|
+
result.output = checkResult.text;
|
|
264
|
+
|
|
265
|
+
if (verbose || !result.success) {
|
|
266
|
+
console.log(`\n${result.success ? '✅' : '❌'} ${name}:`);
|
|
267
|
+
if (result.output) {
|
|
268
|
+
console.log(result.output);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
} catch (err) {
|
|
272
|
+
result.success = false;
|
|
273
|
+
result.error = err.message;
|
|
274
|
+
|
|
275
|
+
if (verbose) {
|
|
276
|
+
console.log(`\n❌ ${name}:`);
|
|
277
|
+
console.log(result.error);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return result;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Generate a summary report of CI check results
|
|
286
|
+
* @param {Object} results - Results from runLocalCIChecks()
|
|
287
|
+
* @returns {string} Formatted summary report
|
|
288
|
+
*/
|
|
289
|
+
export function generateCICheckReport(results) {
|
|
290
|
+
const lines = [];
|
|
291
|
+
|
|
292
|
+
lines.push('\n=== Local CI Checks Summary ===\n');
|
|
293
|
+
|
|
294
|
+
if (results.checks.length === 0) {
|
|
295
|
+
lines.push('No CI checks were run.');
|
|
296
|
+
return lines.join('\n');
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const passed = results.checks.filter(c => c.success).length;
|
|
300
|
+
const failed = results.checks.filter(c => !c.success).length;
|
|
301
|
+
|
|
302
|
+
lines.push(`Total checks: ${results.checks.length}`);
|
|
303
|
+
lines.push(`Passed: ${passed}`);
|
|
304
|
+
lines.push(`Failed: ${failed}`);
|
|
305
|
+
lines.push('');
|
|
306
|
+
|
|
307
|
+
if (failed > 0) {
|
|
308
|
+
lines.push('Failed checks:');
|
|
309
|
+
results.checks.filter(c => !c.success).forEach(check => {
|
|
310
|
+
lines.push(` ❌ ${check.name}`);
|
|
311
|
+
if (check.output) {
|
|
312
|
+
lines.push(` ${check.output.split('\n')[0]}`);
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (results.success) {
|
|
318
|
+
lines.push('\n✅ All CI checks passed! Safe to commit and push.');
|
|
319
|
+
} else {
|
|
320
|
+
lines.push('\n❌ Some CI checks failed. Please fix the issues before committing.');
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return lines.join('\n');
|
|
324
|
+
}
|