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.
Files changed (78) hide show
  1. package/.github/ISSUE_TEMPLATE/persona-request.md +62 -0
  2. package/.github/ISSUE_TEMPLATE/quality-feedback.md +67 -0
  3. package/.github/workflows/cla.yml +25 -0
  4. package/.github/workflows/validate.yml +49 -0
  5. package/.idea/agentboot.iml +9 -0
  6. package/.idea/misc.xml +6 -0
  7. package/.idea/modules.xml +8 -0
  8. package/.idea/vcs.xml +6 -0
  9. package/CLA.md +98 -0
  10. package/CLAUDE.md +230 -0
  11. package/CONTRIBUTING.md +168 -0
  12. package/LICENSE +191 -0
  13. package/NOTICE +4 -0
  14. package/PERSONAS.md +156 -0
  15. package/README.md +172 -0
  16. package/agentboot.config.json +207 -0
  17. package/bin/agentboot.js +17 -0
  18. package/core/gotchas/README.md +35 -0
  19. package/core/instructions/baseline.instructions.md +133 -0
  20. package/core/instructions/security.instructions.md +186 -0
  21. package/core/personas/code-reviewer/SKILL.md +175 -0
  22. package/core/personas/code-reviewer/persona.config.json +11 -0
  23. package/core/personas/security-reviewer/SKILL.md +233 -0
  24. package/core/personas/security-reviewer/persona.config.json +11 -0
  25. package/core/personas/test-data-expert/SKILL.md +234 -0
  26. package/core/personas/test-data-expert/persona.config.json +10 -0
  27. package/core/personas/test-generator/SKILL.md +262 -0
  28. package/core/personas/test-generator/persona.config.json +10 -0
  29. package/core/traits/audit-trail.md +182 -0
  30. package/core/traits/confidence-signaling.md +172 -0
  31. package/core/traits/critical-thinking.md +129 -0
  32. package/core/traits/schema-awareness.md +132 -0
  33. package/core/traits/source-citation.md +174 -0
  34. package/core/traits/structured-output.md +199 -0
  35. package/docs/ci-cd-automation.md +548 -0
  36. package/docs/claude-code-reference/README.md +21 -0
  37. package/docs/claude-code-reference/agentboot-coverage.md +484 -0
  38. package/docs/claude-code-reference/feature-inventory.md +906 -0
  39. package/docs/cli-commands-audit.md +112 -0
  40. package/docs/cli-design.md +924 -0
  41. package/docs/concepts.md +1117 -0
  42. package/docs/config-schema-audit.md +121 -0
  43. package/docs/configuration.md +645 -0
  44. package/docs/delivery-methods.md +758 -0
  45. package/docs/developer-onboarding.md +342 -0
  46. package/docs/extending.md +448 -0
  47. package/docs/getting-started.md +298 -0
  48. package/docs/knowledge-layer.md +464 -0
  49. package/docs/marketplace.md +822 -0
  50. package/docs/org-connection.md +570 -0
  51. package/docs/plans/architecture.md +2429 -0
  52. package/docs/plans/design.md +2018 -0
  53. package/docs/plans/prd.md +1862 -0
  54. package/docs/plans/stack-rank.md +261 -0
  55. package/docs/plans/technical-spec.md +2755 -0
  56. package/docs/privacy-and-safety.md +807 -0
  57. package/docs/prompt-optimization.md +1071 -0
  58. package/docs/test-plan.md +972 -0
  59. package/docs/third-party-ecosystem.md +496 -0
  60. package/domains/compliance-template/README.md +173 -0
  61. package/domains/compliance-template/traits/compliance-aware.md +228 -0
  62. package/examples/enterprise/agentboot.config.json +184 -0
  63. package/examples/minimal/agentboot.config.json +46 -0
  64. package/package.json +63 -0
  65. package/repos.json +1 -0
  66. package/scripts/cli.ts +1069 -0
  67. package/scripts/compile.ts +1000 -0
  68. package/scripts/dev-sync.ts +149 -0
  69. package/scripts/lib/config.ts +137 -0
  70. package/scripts/lib/frontmatter.ts +61 -0
  71. package/scripts/sync.ts +687 -0
  72. package/scripts/validate.ts +421 -0
  73. package/tests/REGRESSION-PLAN.md +705 -0
  74. package/tests/TEST-PLAN.md +111 -0
  75. package/tests/cli.test.ts +705 -0
  76. package/tests/pipeline.test.ts +608 -0
  77. package/tests/validate.test.ts +278 -0
  78. 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
+ });