agentboot 0.1.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/.github/ISSUE_TEMPLATE/persona-request.md +62 -0
- package/.github/ISSUE_TEMPLATE/quality-feedback.md +67 -0
- package/.github/workflows/cla.yml +25 -0
- package/.github/workflows/validate.yml +49 -0
- package/.idea/agentboot.iml +9 -0
- package/.idea/misc.xml +6 -0
- package/.idea/modules.xml +8 -0
- package/.idea/vcs.xml +6 -0
- package/CLA.md +98 -0
- package/CLAUDE.md +230 -0
- package/CONTRIBUTING.md +168 -0
- package/LICENSE +191 -0
- package/NOTICE +4 -0
- package/PERSONAS.md +156 -0
- package/README.md +172 -0
- package/agentboot.config.json +207 -0
- package/bin/agentboot.js +17 -0
- package/core/gotchas/README.md +35 -0
- package/core/instructions/baseline.instructions.md +133 -0
- package/core/instructions/security.instructions.md +186 -0
- package/core/personas/code-reviewer/SKILL.md +175 -0
- package/core/personas/code-reviewer/persona.config.json +11 -0
- package/core/personas/security-reviewer/SKILL.md +233 -0
- package/core/personas/security-reviewer/persona.config.json +11 -0
- package/core/personas/test-data-expert/SKILL.md +234 -0
- package/core/personas/test-data-expert/persona.config.json +10 -0
- package/core/personas/test-generator/SKILL.md +262 -0
- package/core/personas/test-generator/persona.config.json +10 -0
- package/core/traits/audit-trail.md +182 -0
- package/core/traits/confidence-signaling.md +172 -0
- package/core/traits/critical-thinking.md +129 -0
- package/core/traits/schema-awareness.md +132 -0
- package/core/traits/source-citation.md +174 -0
- package/core/traits/structured-output.md +199 -0
- package/docs/ci-cd-automation.md +548 -0
- package/docs/claude-code-reference/README.md +21 -0
- package/docs/claude-code-reference/agentboot-coverage.md +484 -0
- package/docs/claude-code-reference/feature-inventory.md +906 -0
- package/docs/cli-commands-audit.md +112 -0
- package/docs/cli-design.md +924 -0
- package/docs/concepts.md +1117 -0
- package/docs/config-schema-audit.md +121 -0
- package/docs/configuration.md +645 -0
- package/docs/delivery-methods.md +758 -0
- package/docs/developer-onboarding.md +342 -0
- package/docs/extending.md +448 -0
- package/docs/getting-started.md +298 -0
- package/docs/knowledge-layer.md +464 -0
- package/docs/marketplace.md +822 -0
- package/docs/org-connection.md +570 -0
- package/docs/plans/architecture.md +2429 -0
- package/docs/plans/design.md +2018 -0
- package/docs/plans/prd.md +1862 -0
- package/docs/plans/stack-rank.md +261 -0
- package/docs/plans/technical-spec.md +2755 -0
- package/docs/privacy-and-safety.md +807 -0
- package/docs/prompt-optimization.md +1071 -0
- package/docs/test-plan.md +972 -0
- package/docs/third-party-ecosystem.md +496 -0
- package/domains/compliance-template/README.md +173 -0
- package/domains/compliance-template/traits/compliance-aware.md +228 -0
- package/examples/enterprise/agentboot.config.json +184 -0
- package/examples/minimal/agentboot.config.json +46 -0
- package/package.json +63 -0
- package/repos.json +1 -0
- package/scripts/cli.ts +1069 -0
- package/scripts/compile.ts +1000 -0
- package/scripts/dev-sync.ts +149 -0
- package/scripts/lib/config.ts +137 -0
- package/scripts/lib/frontmatter.ts +61 -0
- package/scripts/sync.ts +687 -0
- package/scripts/validate.ts +421 -0
- package/tests/REGRESSION-PLAN.md +705 -0
- package/tests/TEST-PLAN.md +111 -0
- package/tests/cli.test.ts +705 -0
- package/tests/pipeline.test.ts +608 -0
- package/tests/validate.test.ts +278 -0
- package/tsconfig.json +62 -0
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgentBoot validate script.
|
|
3
|
+
*
|
|
4
|
+
* Runs a suite of checks against the AgentBoot source tree and config before
|
|
5
|
+
* a build is allowed to proceed. All checks are independent — every failure
|
|
6
|
+
* is reported before the process exits.
|
|
7
|
+
*
|
|
8
|
+
* Checks:
|
|
9
|
+
* 1. All personas in agentboot.config.json exist in core/personas/
|
|
10
|
+
* 2. All traits referenced in persona configs exist in core/traits/
|
|
11
|
+
* 3. All SKILL.md files have required frontmatter (name, description)
|
|
12
|
+
* 4. No obvious secrets or credentials in trait/persona definitions
|
|
13
|
+
*
|
|
14
|
+
* Usage:
|
|
15
|
+
* npm run validate
|
|
16
|
+
* tsx scripts/validate.ts
|
|
17
|
+
* tsx scripts/validate.ts --config path/to/agentboot.config.json
|
|
18
|
+
* tsx scripts/validate.ts --strict (treats warnings as errors)
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import fs from "node:fs";
|
|
22
|
+
import path from "node:path";
|
|
23
|
+
import { fileURLToPath } from "node:url";
|
|
24
|
+
import chalk from "chalk";
|
|
25
|
+
import {
|
|
26
|
+
type AgentBootConfig,
|
|
27
|
+
type PersonaConfig,
|
|
28
|
+
resolveConfigPath,
|
|
29
|
+
loadConfig,
|
|
30
|
+
stripJsoncComments,
|
|
31
|
+
} from "./lib/config.js";
|
|
32
|
+
import {
|
|
33
|
+
parseFrontmatter,
|
|
34
|
+
DEFAULT_SECRET_PATTERNS,
|
|
35
|
+
scanForSecrets,
|
|
36
|
+
} from "./lib/frontmatter.js";
|
|
37
|
+
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
// Paths
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
43
|
+
const ROOT = path.resolve(__dirname, "..");
|
|
44
|
+
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// Types
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
interface CheckResult {
|
|
50
|
+
name: string;
|
|
51
|
+
passed: boolean;
|
|
52
|
+
warnings: string[];
|
|
53
|
+
errors: string[];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
// Helpers
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
|
|
60
|
+
function check(name: string): CheckResult {
|
|
61
|
+
return { name, passed: true, warnings: [], errors: [] };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function fail(result: CheckResult, msg: string): void {
|
|
65
|
+
result.errors.push(msg);
|
|
66
|
+
result.passed = false;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function warn(result: CheckResult, msg: string): void {
|
|
70
|
+
result.warnings.push(msg);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function printResult(result: CheckResult, strictMode: boolean): void {
|
|
74
|
+
const effectivePassed = result.passed && (strictMode ? result.warnings.length === 0 : true);
|
|
75
|
+
|
|
76
|
+
if (effectivePassed) {
|
|
77
|
+
console.log(` ${chalk.green("✓")} ${result.name}`);
|
|
78
|
+
} else {
|
|
79
|
+
console.log(` ${chalk.red("✗")} ${result.name}`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
for (const err of result.errors) {
|
|
83
|
+
console.log(chalk.red(` ERROR: ${err}`));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
for (const w of result.warnings) {
|
|
87
|
+
const icon = strictMode ? chalk.red("WARN (strict)") : chalk.yellow("WARN");
|
|
88
|
+
console.log(` ${icon}: ${w}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function isEffectiveFail(result: CheckResult, strictMode: boolean): boolean {
|
|
93
|
+
if (!result.passed) return true;
|
|
94
|
+
if (strictMode && result.warnings.length > 0) return true;
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
// Check 1: Persona existence
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
|
|
102
|
+
function checkPersonaExistence(config: AgentBootConfig, configDir: string): CheckResult {
|
|
103
|
+
const result = check("Persona existence — all enabled personas found in core/personas/");
|
|
104
|
+
const enabledPersonas = config.personas?.enabled;
|
|
105
|
+
|
|
106
|
+
if (!enabledPersonas || enabledPersonas.length === 0) {
|
|
107
|
+
warn(result, "No personas enabled in config. Nothing will be compiled.");
|
|
108
|
+
return result;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const corePersonasDir = path.join(ROOT, "core", "personas");
|
|
112
|
+
const extendDir = config.personas?.customDir
|
|
113
|
+
? path.resolve(configDir, config.personas.extend)
|
|
114
|
+
: null;
|
|
115
|
+
|
|
116
|
+
// Collect all available persona directories.
|
|
117
|
+
const available = new Set<string>();
|
|
118
|
+
|
|
119
|
+
if (fs.existsSync(corePersonasDir)) {
|
|
120
|
+
for (const entry of fs.readdirSync(corePersonasDir)) {
|
|
121
|
+
if (fs.statSync(path.join(corePersonasDir, entry)).isDirectory()) {
|
|
122
|
+
available.add(entry);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (extendDir && fs.existsSync(extendDir)) {
|
|
128
|
+
for (const entry of fs.readdirSync(extendDir)) {
|
|
129
|
+
if (fs.statSync(path.join(extendDir, entry)).isDirectory()) {
|
|
130
|
+
available.add(entry);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
for (const persona of enabledPersonas) {
|
|
136
|
+
if (!available.has(persona)) {
|
|
137
|
+
fail(
|
|
138
|
+
result,
|
|
139
|
+
`Persona "${persona}" is enabled in config but no directory found. ` +
|
|
140
|
+
`Expected: core/personas/${persona}/ or ${config.personas?.customDir ?? "(no extend path)"}/${persona}/`
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (result.passed) {
|
|
146
|
+
// Also warn about personas that exist but are not enabled.
|
|
147
|
+
const disabled = [...available].filter((p) => !enabledPersonas.includes(p));
|
|
148
|
+
if (disabled.length > 0) {
|
|
149
|
+
warn(result, `Personas in core/ not enabled: ${disabled.join(", ")}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return result;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ---------------------------------------------------------------------------
|
|
157
|
+
// Check 2: Trait references
|
|
158
|
+
// ---------------------------------------------------------------------------
|
|
159
|
+
|
|
160
|
+
function checkTraitReferences(config: AgentBootConfig, configDir: string): CheckResult {
|
|
161
|
+
const result = check(
|
|
162
|
+
"Trait references — all persona.config.json trait entries exist in core/traits/"
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
const coreTraitsDir = path.join(ROOT, "core", "traits");
|
|
166
|
+
const enabledTraits = config.traits?.enabled;
|
|
167
|
+
|
|
168
|
+
// Collect available trait names.
|
|
169
|
+
const availableTraits = new Set<string>();
|
|
170
|
+
if (fs.existsSync(coreTraitsDir)) {
|
|
171
|
+
for (const file of fs.readdirSync(coreTraitsDir)) {
|
|
172
|
+
if (file.endsWith(".md")) {
|
|
173
|
+
availableTraits.add(path.basename(file, ".md"));
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (availableTraits.size === 0) {
|
|
179
|
+
warn(result, "No trait files found in core/traits/. Trait injection will be skipped.");
|
|
180
|
+
return result;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Scan all persona.config.json files.
|
|
184
|
+
const personaRoots: string[] = [path.join(ROOT, "core", "personas")];
|
|
185
|
+
if (config.personas?.customDir) {
|
|
186
|
+
const ext = path.resolve(configDir, config.personas.extend);
|
|
187
|
+
if (fs.existsSync(ext)) personaRoots.push(ext);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
for (const root of personaRoots) {
|
|
191
|
+
if (!fs.existsSync(root)) continue;
|
|
192
|
+
|
|
193
|
+
for (const personaName of fs.readdirSync(root)) {
|
|
194
|
+
const personaDir = path.join(root, personaName);
|
|
195
|
+
if (!fs.statSync(personaDir).isDirectory()) continue;
|
|
196
|
+
|
|
197
|
+
const configPath = path.join(personaDir, "persona.config.json");
|
|
198
|
+
if (!fs.existsSync(configPath)) continue;
|
|
199
|
+
|
|
200
|
+
let personaConfig: PersonaConfig;
|
|
201
|
+
try {
|
|
202
|
+
personaConfig = JSON.parse(stripJsoncComments(fs.readFileSync(configPath, "utf-8"))) as PersonaConfig;
|
|
203
|
+
} catch {
|
|
204
|
+
fail(result, `[${personaName}] persona.config.json is not valid JSON`);
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Collect all trait references in this persona config.
|
|
209
|
+
const traitRefs = new Set<string>();
|
|
210
|
+
for (const t of personaConfig.traits ?? []) traitRefs.add(t);
|
|
211
|
+
for (const g of Object.values(personaConfig.groups ?? {})) {
|
|
212
|
+
for (const t of g.traits ?? []) traitRefs.add(t);
|
|
213
|
+
}
|
|
214
|
+
for (const tm of Object.values(personaConfig.teams ?? {})) {
|
|
215
|
+
for (const t of tm.traits ?? []) traitRefs.add(t);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
for (const traitRef of traitRefs) {
|
|
219
|
+
if (!availableTraits.has(traitRef)) {
|
|
220
|
+
fail(
|
|
221
|
+
result,
|
|
222
|
+
`[${personaName}] References trait "${traitRef}" which does not exist in core/traits/`
|
|
223
|
+
);
|
|
224
|
+
} else if (enabledTraits && !enabledTraits.includes(traitRef)) {
|
|
225
|
+
warn(
|
|
226
|
+
result,
|
|
227
|
+
`[${personaName}] References trait "${traitRef}" which exists but is not in traits.enabled`
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return result;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// ---------------------------------------------------------------------------
|
|
238
|
+
// Check 3: SKILL.md frontmatter
|
|
239
|
+
// ---------------------------------------------------------------------------
|
|
240
|
+
|
|
241
|
+
function checkSkillFrontmatter(config: AgentBootConfig, configDir: string): CheckResult {
|
|
242
|
+
const result = check("SKILL.md frontmatter — required fields present (name, description)");
|
|
243
|
+
|
|
244
|
+
const personaRoots: string[] = [path.join(ROOT, "core", "personas")];
|
|
245
|
+
if (config.personas?.customDir) {
|
|
246
|
+
const ext = path.resolve(configDir, config.personas.extend);
|
|
247
|
+
if (fs.existsSync(ext)) personaRoots.push(ext);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
let skillsChecked = 0;
|
|
251
|
+
|
|
252
|
+
for (const root of personaRoots) {
|
|
253
|
+
if (!fs.existsSync(root)) continue;
|
|
254
|
+
|
|
255
|
+
for (const personaName of fs.readdirSync(root)) {
|
|
256
|
+
const personaDir = path.join(root, personaName);
|
|
257
|
+
if (!fs.statSync(personaDir).isDirectory()) continue;
|
|
258
|
+
|
|
259
|
+
const skillPath = path.join(personaDir, "SKILL.md");
|
|
260
|
+
if (!fs.existsSync(skillPath)) {
|
|
261
|
+
warn(result, `[${personaName}] No SKILL.md found`);
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
skillsChecked++;
|
|
266
|
+
const content = fs.readFileSync(skillPath, "utf-8");
|
|
267
|
+
const fields = parseFrontmatter(content);
|
|
268
|
+
|
|
269
|
+
if (!fields) {
|
|
270
|
+
fail(
|
|
271
|
+
result,
|
|
272
|
+
`[${personaName}] SKILL.md has no frontmatter block (expected ---\\n...\\n--- at top of file)`
|
|
273
|
+
);
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (!fields.has("name") || fields.get("name") === "") {
|
|
278
|
+
fail(result, `[${personaName}] SKILL.md frontmatter missing required field: name`);
|
|
279
|
+
}
|
|
280
|
+
if (!fields.has("description") || fields.get("description") === "") {
|
|
281
|
+
fail(result, `[${personaName}] SKILL.md frontmatter missing required field: description`);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (skillsChecked === 0) {
|
|
287
|
+
warn(result, "No SKILL.md files found. Has the persona directory been populated?");
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return result;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// ---------------------------------------------------------------------------
|
|
294
|
+
// Check 4: Secret / credential scan
|
|
295
|
+
// ---------------------------------------------------------------------------
|
|
296
|
+
|
|
297
|
+
function buildSecretPatterns(config: AgentBootConfig): RegExp[] {
|
|
298
|
+
const configPatterns = (config.validation?.secretPatterns ?? []).map(
|
|
299
|
+
(p) => new RegExp(p)
|
|
300
|
+
);
|
|
301
|
+
return [...DEFAULT_SECRET_PATTERNS, ...configPatterns];
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function checkNoSecrets(config: AgentBootConfig, configDir: string): CheckResult {
|
|
305
|
+
const result = check("Secret scan — no credentials or keys in trait/persona definitions");
|
|
306
|
+
const patterns = buildSecretPatterns(config);
|
|
307
|
+
|
|
308
|
+
const scanRoots: string[] = [
|
|
309
|
+
path.join(ROOT, "core", "traits"),
|
|
310
|
+
path.join(ROOT, "core", "personas"),
|
|
311
|
+
];
|
|
312
|
+
|
|
313
|
+
if (config.personas?.customDir) {
|
|
314
|
+
const ext = path.resolve(configDir, config.personas.extend);
|
|
315
|
+
if (fs.existsSync(ext)) scanRoots.push(ext);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
for (const root of scanRoots) {
|
|
319
|
+
if (!fs.existsSync(root)) continue;
|
|
320
|
+
|
|
321
|
+
// Recursively find all .md and .json files.
|
|
322
|
+
const files = walkDir(root, [".md", ".json"]);
|
|
323
|
+
|
|
324
|
+
for (const filePath of files) {
|
|
325
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
326
|
+
const hits = scanForSecrets(content, patterns);
|
|
327
|
+
for (const hit of hits) {
|
|
328
|
+
fail(
|
|
329
|
+
result,
|
|
330
|
+
`Potential secret at ${path.relative(ROOT, filePath)}:${hit.line} ` +
|
|
331
|
+
`(matched pattern: ${hit.pattern})`
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return result;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function walkDir(dir: string, extensions: string[]): string[] {
|
|
341
|
+
const results: string[] = [];
|
|
342
|
+
if (!fs.existsSync(dir)) return results;
|
|
343
|
+
|
|
344
|
+
for (const entry of fs.readdirSync(dir)) {
|
|
345
|
+
const full = path.join(dir, entry);
|
|
346
|
+
const stat = fs.statSync(full);
|
|
347
|
+
if (stat.isDirectory()) {
|
|
348
|
+
results.push(...walkDir(full, extensions));
|
|
349
|
+
} else if (extensions.some((ext) => full.endsWith(ext))) {
|
|
350
|
+
results.push(full);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
return results;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// ---------------------------------------------------------------------------
|
|
358
|
+
// Main
|
|
359
|
+
// ---------------------------------------------------------------------------
|
|
360
|
+
|
|
361
|
+
async function main(): Promise<void> {
|
|
362
|
+
const argv = process.argv.slice(2);
|
|
363
|
+
const configPath = resolveConfigPath(argv, ROOT);
|
|
364
|
+
const forceStrict = argv.includes("--strict");
|
|
365
|
+
|
|
366
|
+
console.log(chalk.bold("\nAgentBoot — validate"));
|
|
367
|
+
console.log(chalk.gray(`Config: ${configPath}\n`));
|
|
368
|
+
|
|
369
|
+
const config = loadConfig(configPath);
|
|
370
|
+
const configDir = path.dirname(configPath);
|
|
371
|
+
const strictMode = forceStrict || (config.validation?.strictMode ?? false);
|
|
372
|
+
|
|
373
|
+
if (strictMode) {
|
|
374
|
+
console.log(chalk.yellow(" ⚑ Strict mode: warnings treated as errors\n"));
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Run all checks.
|
|
378
|
+
const checks: CheckResult[] = [
|
|
379
|
+
checkPersonaExistence(config, configDir),
|
|
380
|
+
checkTraitReferences(config, configDir),
|
|
381
|
+
checkSkillFrontmatter(config, configDir),
|
|
382
|
+
checkNoSecrets(config, configDir),
|
|
383
|
+
];
|
|
384
|
+
|
|
385
|
+
// Print results.
|
|
386
|
+
for (const c of checks) {
|
|
387
|
+
printResult(c, strictMode);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Summary.
|
|
391
|
+
const failures = checks.filter((c) => isEffectiveFail(c, strictMode));
|
|
392
|
+
const warnings = checks.reduce((acc, c) => acc + c.warnings.length, 0);
|
|
393
|
+
|
|
394
|
+
console.log("");
|
|
395
|
+
if (failures.length === 0) {
|
|
396
|
+
console.log(
|
|
397
|
+
chalk.bold(
|
|
398
|
+
chalk.green(`✓ All ${checks.length} checks passed`) +
|
|
399
|
+
(warnings > 0 ? chalk.yellow(` (${warnings} warning${warnings > 1 ? "s" : ""})`) : "")
|
|
400
|
+
)
|
|
401
|
+
);
|
|
402
|
+
process.exit(0);
|
|
403
|
+
} else {
|
|
404
|
+
const errorCount = failures.reduce((acc, c) => acc + c.errors.length, 0);
|
|
405
|
+
console.log(
|
|
406
|
+
chalk.bold(
|
|
407
|
+
chalk.red(
|
|
408
|
+
`✗ ${failures.length} check${failures.length > 1 ? "s" : ""} failed ` +
|
|
409
|
+
`(${errorCount} error${errorCount > 1 ? "s" : ""}, ` +
|
|
410
|
+
`${warnings} warning${warnings > 1 ? "s" : ""})`
|
|
411
|
+
)
|
|
412
|
+
)
|
|
413
|
+
);
|
|
414
|
+
process.exit(1);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
main().catch((err: unknown) => {
|
|
419
|
+
console.error(chalk.red("Unexpected error:"), err);
|
|
420
|
+
process.exit(1);
|
|
421
|
+
});
|