@mainahq/core 0.7.0 → 1.0.1

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/src/init/index.ts CHANGED
@@ -6,7 +6,13 @@
6
6
  * Never overwrites existing files unless `force: true`.
7
7
  */
8
8
 
9
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
9
+ import {
10
+ existsSync,
11
+ mkdirSync,
12
+ readdirSync,
13
+ readFileSync,
14
+ writeFileSync,
15
+ } from "node:fs";
10
16
  import { join } from "node:path";
11
17
  import type { Result } from "../db/index";
12
18
  import type { DetectedTool } from "../verify/detect";
@@ -16,6 +22,7 @@ import { detectTools } from "../verify/detect";
16
22
 
17
23
  export interface InitOptions {
18
24
  force?: boolean;
25
+ aiGenerate?: boolean;
19
26
  }
20
27
 
21
28
  export interface InitReport {
@@ -24,11 +31,13 @@ export interface InitReport {
24
31
  directory: string;
25
32
  detectedStack: DetectedStack;
26
33
  detectedTools: DetectedTool[];
34
+ aiGenerated?: boolean;
27
35
  }
28
36
 
29
37
  export interface DetectedStack {
30
38
  runtime: "bun" | "node" | "deno" | "unknown";
31
39
  language: "typescript" | "javascript" | "unknown";
40
+ languages: string[];
32
41
  testRunner: string;
33
42
  linter: string;
34
43
  framework: string;
@@ -40,89 +49,168 @@ function detectStack(repoRoot: string): DetectedStack {
40
49
  const stack: DetectedStack = {
41
50
  runtime: "unknown",
42
51
  language: "unknown",
52
+ languages: [],
43
53
  testRunner: "unknown",
44
54
  linter: "unknown",
45
55
  framework: "none",
46
56
  };
47
57
 
48
- // Try reading package.json
49
- const pkgPath = join(repoRoot, "package.json");
50
- if (!existsSync(pkgPath)) return stack;
58
+ // ── Multi-language detection (file-marker based) ─────────────────────
59
+ const languages: string[] = [];
51
60
 
52
- let pkg: Record<string, unknown>;
53
- try {
54
- pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
55
- } catch {
56
- return stack;
61
+ // Go
62
+ if (existsSync(join(repoRoot, "go.mod"))) {
63
+ languages.push("go");
57
64
  }
58
65
 
59
- const allDeps = {
60
- ...(pkg.dependencies as Record<string, string> | undefined),
61
- ...(pkg.devDependencies as Record<string, string> | undefined),
62
- ...(pkg.peerDependencies as Record<string, string> | undefined),
63
- };
66
+ // Rust
67
+ if (existsSync(join(repoRoot, "Cargo.toml"))) {
68
+ languages.push("rust");
69
+ }
64
70
 
65
- // Runtime detection
71
+ // Python
66
72
  if (
67
- allDeps["@types/bun"] ||
68
- allDeps["bun-types"] ||
69
- existsSync(join(repoRoot, "bun.lock"))
73
+ existsSync(join(repoRoot, "pyproject.toml")) ||
74
+ existsSync(join(repoRoot, "requirements.txt")) ||
75
+ existsSync(join(repoRoot, "setup.py"))
70
76
  ) {
71
- stack.runtime = "bun";
72
- } else if (
73
- existsSync(join(repoRoot, "deno.json")) ||
74
- existsSync(join(repoRoot, "deno.jsonc"))
75
- ) {
76
- stack.runtime = "deno";
77
- } else {
78
- stack.runtime = "node";
77
+ languages.push("python");
79
78
  }
80
79
 
81
- // Language detection
82
- if (existsSync(join(repoRoot, "tsconfig.json")) || allDeps.typescript) {
83
- stack.language = "typescript";
84
- } else {
85
- stack.language = "javascript";
80
+ // Java
81
+ if (
82
+ existsSync(join(repoRoot, "pom.xml")) ||
83
+ existsSync(join(repoRoot, "build.gradle")) ||
84
+ existsSync(join(repoRoot, "build.gradle.kts"))
85
+ ) {
86
+ languages.push("java");
86
87
  }
87
88
 
88
- // Test runner detection
89
- if (stack.runtime === "bun") {
90
- stack.testRunner = "bun:test";
91
- } else if (allDeps.vitest) {
92
- stack.testRunner = "vitest";
93
- } else if (allDeps.jest || allDeps["@jest/core"]) {
94
- stack.testRunner = "jest";
95
- } else if (allDeps.mocha) {
96
- stack.testRunner = "mocha";
89
+ // .NET (C#/F#) — check for .csproj, .fsproj, .sln files
90
+ try {
91
+ const entries = readdirSync(repoRoot);
92
+ if (
93
+ entries.some(
94
+ (e: string) =>
95
+ e.endsWith(".csproj") || e.endsWith(".sln") || e.endsWith(".fsproj"),
96
+ )
97
+ ) {
98
+ languages.push("dotnet");
99
+ }
100
+ } catch {
101
+ // Directory not readable — skip
97
102
  }
98
103
 
99
- // Linter detection
100
- if (allDeps["@biomejs/biome"]) {
101
- stack.linter = "biome";
102
- } else if (allDeps.eslint) {
103
- stack.linter = "eslint";
104
- } else if (allDeps.prettier) {
105
- stack.linter = "prettier";
104
+ // ── JS/TS detection from package.json ───────────────────────────────
105
+ const pkgPath = join(repoRoot, "package.json");
106
+ let hasPkgJson = false;
107
+ let allDeps: Record<string, string> = {};
108
+
109
+ if (existsSync(pkgPath)) {
110
+ try {
111
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8")) as Record<
112
+ string,
113
+ unknown
114
+ >;
115
+ hasPkgJson = true;
116
+ allDeps = {
117
+ ...(pkg.dependencies as Record<string, string> | undefined),
118
+ ...(pkg.devDependencies as Record<string, string> | undefined),
119
+ ...(pkg.peerDependencies as Record<string, string> | undefined),
120
+ };
121
+ } catch {
122
+ // Malformed package.json — skip
123
+ }
106
124
  }
107
125
 
108
- // Framework detection
109
- if (allDeps.next) {
110
- stack.framework = "next.js";
111
- } else if (allDeps.express) {
112
- stack.framework = "express";
113
- } else if (allDeps.hono) {
114
- stack.framework = "hono";
115
- } else if (allDeps.react && !allDeps.next) {
116
- stack.framework = "react";
117
- } else if (allDeps.vue) {
118
- stack.framework = "vue";
119
- } else if (allDeps.svelte) {
120
- stack.framework = "svelte";
126
+ if (hasPkgJson) {
127
+ // Runtime detection
128
+ if (
129
+ allDeps["@types/bun"] ||
130
+ allDeps["bun-types"] ||
131
+ existsSync(join(repoRoot, "bun.lock"))
132
+ ) {
133
+ stack.runtime = "bun";
134
+ } else if (
135
+ existsSync(join(repoRoot, "deno.json")) ||
136
+ existsSync(join(repoRoot, "deno.jsonc"))
137
+ ) {
138
+ stack.runtime = "deno";
139
+ } else {
140
+ stack.runtime = "node";
141
+ }
142
+
143
+ // Language detection (single primary)
144
+ if (existsSync(join(repoRoot, "tsconfig.json")) || allDeps.typescript) {
145
+ stack.language = "typescript";
146
+ if (!languages.includes("typescript")) {
147
+ languages.push("typescript");
148
+ }
149
+ } else {
150
+ stack.language = "javascript";
151
+ if (!languages.includes("javascript")) {
152
+ languages.push("javascript");
153
+ }
154
+ }
155
+
156
+ // Test runner detection
157
+ if (stack.runtime === "bun") {
158
+ stack.testRunner = "bun:test";
159
+ } else if (allDeps.vitest) {
160
+ stack.testRunner = "vitest";
161
+ } else if (allDeps.jest || allDeps["@jest/core"]) {
162
+ stack.testRunner = "jest";
163
+ } else if (allDeps.mocha) {
164
+ stack.testRunner = "mocha";
165
+ }
166
+
167
+ // Linter detection
168
+ if (allDeps["@biomejs/biome"]) {
169
+ stack.linter = "biome";
170
+ } else if (allDeps.eslint) {
171
+ stack.linter = "eslint";
172
+ } else if (allDeps.prettier) {
173
+ stack.linter = "prettier";
174
+ }
175
+
176
+ // Framework detection
177
+ if (allDeps.next) {
178
+ stack.framework = "next.js";
179
+ } else if (allDeps.express) {
180
+ stack.framework = "express";
181
+ } else if (allDeps.hono) {
182
+ stack.framework = "hono";
183
+ } else if (allDeps.react && !allDeps.next) {
184
+ stack.framework = "react";
185
+ } else if (allDeps.vue) {
186
+ stack.framework = "vue";
187
+ } else if (allDeps.svelte) {
188
+ stack.framework = "svelte";
189
+ }
121
190
  }
122
191
 
192
+ // If no languages detected, mark as unknown
193
+ stack.languages = languages.length > 0 ? languages : ["unknown"];
194
+
123
195
  return stack;
124
196
  }
125
197
 
198
+ // ── Constants (shared across agent files) ───────────────────────────────────
199
+
200
+ const WORKFLOW_ORDER =
201
+ "brainstorm -> ticket -> plan -> design -> spec -> implement -> verify -> review -> fix -> commit -> review -> pr";
202
+
203
+ const MCP_TOOLS_TABLE = `| Tool | When to use |
204
+ |------|-------------|
205
+ | \`getContext\` | Before starting — understand branch state and verification status |
206
+ | \`verify\` | After changes — run the full verification pipeline |
207
+ | \`checkSlop\` | On changed files — detect AI-generated slop patterns |
208
+ | \`reviewCode\` | On your diff — two-stage review (spec compliance + code quality) |
209
+ | \`suggestTests\` | When implementing — generate TDD test stubs |
210
+ | \`getConventions\` | Understand project coding conventions |
211
+ | \`explainModule\` | Understand a module's purpose and dependencies |
212
+ | \`analyzeFeature\` | Analyze a feature directory for consistency |`;
213
+
126
214
  // ── Templates ────────────────────────────────────────────────────────────────
127
215
 
128
216
  function buildConstitution(stack: DetectedStack): string {
@@ -177,7 +265,12 @@ function buildAgentsMd(stack: DetectedStack): string {
177
265
 
178
266
  return `# AGENTS.md
179
267
 
180
- This repo uses [Maina](https://github.com/beeeku/maina) for verification-first development.
268
+ This repo uses [Maina](https://github.com/mainahq/maina) for verification-first development.
269
+
270
+ ## Workflow Order
271
+
272
+ Follow this order for every feature:
273
+ \`${WORKFLOW_ORDER}\`
181
274
 
182
275
  ## Quick Start
183
276
  \`\`\`bash
@@ -199,12 +292,20 @@ maina commit # verify + commit
199
292
  | \`maina stats\` | Show verification metrics |
200
293
  | \`maina doctor\` | Check tool health |
201
294
 
295
+ ## MCP Tools
296
+
297
+ ${MCP_TOOLS_TABLE}
298
+
202
299
  ## Config Files
203
300
  | File | Purpose | Who Edits |
204
301
  |------|---------|-----------|
205
302
  | \`.maina/constitution.md\` | Project DNA — stack, rules, gates | Team (stable, rarely changes) |
206
303
  | \`AGENTS.md\` | Agent instructions — commands, conventions | Team |
304
+ | \`.github/copilot-instructions.md\` | Copilot agent instructions + MCP tools | Team |
207
305
  | \`CLAUDE.md\` | Claude Code specific instructions | Optional, Claude Code users |
306
+ | \`GEMINI.md\` | Gemini CLI specific instructions | Optional, Gemini CLI users |
307
+ | \`.cursorrules\` | Cursor specific instructions | Optional, Cursor users |
308
+ | \`.mcp.json\` | MCP server configuration | Team |
208
309
  | \`.maina/prompts/*.md\` | Prompt overrides for review/commit/etc | Maina (via \`maina learn\`) |
209
310
 
210
311
  ## Runtime
@@ -213,6 +314,279 @@ maina commit # verify + commit
213
314
  `;
214
315
  }
215
316
 
317
+ function buildCopilotInstructions(stack: DetectedStack): string {
318
+ const runCmd = stack.runtime === "bun" ? "bun" : "npm";
319
+ return `# Copilot Instructions
320
+
321
+ You are working on a codebase verified by [Maina](https://mainahq.com), the verification-first developer OS. Maina MCP tools are available — use them.
322
+
323
+ ## Workflow Order
324
+
325
+ Follow this order for every feature:
326
+ \`${WORKFLOW_ORDER}\`
327
+
328
+ ## Step-by-step
329
+
330
+ 1. **Get context** — call \`maina getContext\` to understand codebase state
331
+ 2. **Write tests first** — TDD always. Write failing tests, then implement
332
+ 3. **Verify your work** — call \`maina verify\` before requesting review
333
+ 4. **Check for slop** — call \`maina checkSlop\` on changed files
334
+ 5. **Review your code** — call \`maina reviewCode\` with your diff
335
+
336
+ ## Available MCP Tools
337
+
338
+ ${MCP_TOOLS_TABLE}
339
+
340
+ ## Conventions
341
+
342
+ - Runtime: ${stack.runtime}
343
+ - Test: \`${runCmd} test\`
344
+ - Commits: conventional commits (feat, fix, refactor, test, docs, chore)
345
+ - No \`console.log\` in production code
346
+ - Diff-only: only fix issues on changed lines
347
+
348
+ ## When Working on Audit Issues
349
+
350
+ Issues labeled \`audit\` come from maina's daily verification. Fix the specific findings listed — don't refactor unrelated code.
351
+ `;
352
+ }
353
+
354
+ // ── .mcp.json ───────────────────────────────────────────────────────────────
355
+
356
+ function buildMcpJson(): string {
357
+ return JSON.stringify(
358
+ {
359
+ mcpServers: {
360
+ maina: {
361
+ command: "maina",
362
+ args: ["--mcp"],
363
+ },
364
+ },
365
+ },
366
+ null,
367
+ 2,
368
+ );
369
+ }
370
+
371
+ // ── Agent Instruction Files ─────────────────────────────────────────────────
372
+
373
+ function buildClaudeMd(stack: DetectedStack): string {
374
+ const runCmd = stack.runtime === "bun" ? "bun" : "npm";
375
+ return `# CLAUDE.md
376
+
377
+ This repo uses [Maina](https://mainahq.com) for verification-first development.
378
+ Read \`.maina/constitution.md\` for project DNA — stack rules, conventions, and gates.
379
+
380
+ ## Maina Workflow
381
+
382
+ Follow this order for every feature:
383
+ \`${WORKFLOW_ORDER}\`
384
+
385
+ ## MCP Tools
386
+
387
+ Maina exposes MCP tools — use them in every session:
388
+
389
+ ${MCP_TOOLS_TABLE}
390
+
391
+ ## Commands
392
+
393
+ \`\`\`bash
394
+ maina verify # run full verification pipeline
395
+ maina commit # verify + commit
396
+ maina review # two-stage code review
397
+ maina context # generate focused codebase context
398
+ maina doctor # check tool health
399
+ maina plan # create feature with spec/plan/tasks
400
+ maina stats # show verification metrics
401
+ \`\`\`
402
+
403
+ ## Conventions
404
+
405
+ - Runtime: ${stack.runtime}
406
+ - Test: \`${runCmd} test\`
407
+ - Conventional commits (feat, fix, refactor, test, docs, chore)
408
+ - No \`console.log\` in production code
409
+ - Diff-only: only fix issues on changed lines
410
+ - TDD always — write tests first
411
+ `;
412
+ }
413
+
414
+ function buildGeminiMd(stack: DetectedStack): string {
415
+ const runCmd = stack.runtime === "bun" ? "bun" : "npm";
416
+ return `# GEMINI.md
417
+
418
+ Instructions for Gemini CLI when working in this repository.
419
+
420
+ This repo uses [Maina](https://mainahq.com) for verification-first development.
421
+ Read \`.maina/constitution.md\` for project DNA — stack rules, conventions, and gates.
422
+
423
+ ## Maina Workflow
424
+
425
+ Follow this order for every feature:
426
+ \`${WORKFLOW_ORDER}\`
427
+
428
+ ## MCP Tools
429
+
430
+ Maina exposes MCP tools via \`.mcp.json\`. Use them:
431
+
432
+ ${MCP_TOOLS_TABLE}
433
+
434
+ ## Key Commands
435
+
436
+ - \`maina verify\` — run full verification pipeline
437
+ - \`maina commit\` — verify + commit
438
+ - \`maina review\` — two-stage code review
439
+ - \`maina context\` — generate focused codebase context
440
+ - \`maina doctor\` — check tool health
441
+
442
+ ## Rules
443
+
444
+ - Runtime: ${stack.runtime}
445
+ - Test: \`${runCmd} test\`
446
+ - Conventional commits (feat, fix, refactor, test, docs, chore)
447
+ - No \`console.log\` in production code
448
+ - Diff-only: only report findings on changed lines
449
+ - TDD always — write tests first
450
+ `;
451
+ }
452
+
453
+ function buildCursorRules(stack: DetectedStack): string {
454
+ const runCmd = stack.runtime === "bun" ? "bun" : "npm";
455
+ return `# Cursor Rules
456
+
457
+ This repo uses Maina for verification-first development.
458
+ Read \`.maina/constitution.md\` for project DNA.
459
+
460
+ ## Workflow Order
461
+ ${WORKFLOW_ORDER}
462
+
463
+ ## MCP Tools (via .mcp.json)
464
+ ${MCP_TOOLS_TABLE}
465
+
466
+ ## Commands
467
+ - maina verify — run full verification pipeline
468
+ - maina commit — verify + commit
469
+ - maina review — two-stage code review
470
+ - maina context — generate focused codebase context
471
+
472
+ ## Conventions
473
+ - Runtime: ${stack.runtime}
474
+ - Test: ${runCmd} test
475
+ - Conventional commits
476
+ - No console.log in production
477
+ - Diff-only: only report findings on changed lines
478
+ - TDD: write tests first, then implement
479
+ `;
480
+ }
481
+
482
+ // ── AI-Generated Constitution ───────────────────────────────────────────────
483
+
484
+ function buildProjectSummary(repoRoot: string, stack: DetectedStack): string {
485
+ const parts: string[] = [];
486
+ parts.push("## Detected Project Stack");
487
+ parts.push(`- Runtime: ${stack.runtime}`);
488
+ parts.push(`- Primary language: ${stack.language}`);
489
+ parts.push(`- All languages: ${stack.languages.join(", ")}`);
490
+ parts.push(`- Test runner: ${stack.testRunner}`);
491
+ parts.push(`- Linter: ${stack.linter}`);
492
+ parts.push(`- Framework: ${stack.framework}`);
493
+
494
+ // Read package.json for extra context
495
+ const pkgPath = join(repoRoot, "package.json");
496
+ if (existsSync(pkgPath)) {
497
+ try {
498
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8")) as Record<
499
+ string,
500
+ unknown
501
+ >;
502
+ const deps = Object.keys(
503
+ (pkg.dependencies as Record<string, string>) ?? {},
504
+ );
505
+ const devDeps = Object.keys(
506
+ (pkg.devDependencies as Record<string, string>) ?? {},
507
+ );
508
+ if (deps.length > 0) {
509
+ parts.push(`\n## Dependencies\n${deps.join(", ")}`);
510
+ }
511
+ if (devDeps.length > 0) {
512
+ parts.push(`\n## Dev Dependencies\n${devDeps.join(", ")}`);
513
+ }
514
+ if (pkg.description) {
515
+ parts.push(`\n## Project Description\n${pkg.description}`);
516
+ }
517
+ } catch {
518
+ // Ignore parse errors
519
+ }
520
+ }
521
+
522
+ // Check for common config files
523
+ const configFiles: string[] = [];
524
+ const checks = [
525
+ "tsconfig.json",
526
+ "biome.json",
527
+ ".eslintrc.json",
528
+ "jest.config.ts",
529
+ "vitest.config.ts",
530
+ "Dockerfile",
531
+ "docker-compose.yml",
532
+ ".env.example",
533
+ "Makefile",
534
+ ];
535
+ for (const f of checks) {
536
+ if (existsSync(join(repoRoot, f))) {
537
+ configFiles.push(f);
538
+ }
539
+ }
540
+ if (configFiles.length > 0) {
541
+ parts.push(`\n## Config Files Found\n${configFiles.join(", ")}`);
542
+ }
543
+
544
+ return parts.join("\n");
545
+ }
546
+
547
+ async function tryGenerateConstitution(
548
+ repoRoot: string,
549
+ stack: DetectedStack,
550
+ ): Promise<string | null> {
551
+ try {
552
+ const { tryAIGenerate } = await import("../ai/try-generate");
553
+ const mainaDir = join(repoRoot, ".maina");
554
+ const summary = buildProjectSummary(repoRoot, stack);
555
+
556
+ const result = await tryAIGenerate(
557
+ "init-constitution",
558
+ mainaDir,
559
+ {
560
+ stack_runtime: stack.runtime,
561
+ stack_language: stack.language,
562
+ stack_languages: stack.languages.join(", "),
563
+ stack_testRunner: stack.testRunner,
564
+ stack_linter: stack.linter,
565
+ stack_framework: stack.framework,
566
+ },
567
+ `Generate a project constitution for this software project based on the detected stack information below.
568
+
569
+ A constitution defines non-negotiable rules injected into every AI call. It should include:
570
+ 1. Stack section — runtime, language, linter, test runner, framework
571
+ 2. Architecture section — key architectural constraints (infer from the stack)
572
+ 3. Verification section — what must pass before code merges
573
+ 4. Conventions section — coding conventions (infer from the stack)
574
+
575
+ Replace [NEEDS CLARIFICATION] placeholders with reasonable defaults based on the stack.
576
+ Keep it concise (under 50 lines). Use markdown format starting with "# Project Constitution".
577
+
578
+ ${summary}`,
579
+ );
580
+
581
+ if (result.fromAI && result.text) {
582
+ return result.text;
583
+ }
584
+ } catch {
585
+ // AI unavailable — fall back to static template
586
+ }
587
+ return null;
588
+ }
589
+
216
590
  const REVIEW_PROMPT_TEMPLATE = `# Review Prompt
217
591
 
218
592
  Review the following code changes for:
@@ -266,11 +640,14 @@ interface FileEntry {
266
640
  content: string;
267
641
  }
268
642
 
269
- function getFileManifest(stack: DetectedStack): FileEntry[] {
643
+ function getFileManifest(
644
+ stack: DetectedStack,
645
+ constitutionOverride?: string,
646
+ ): FileEntry[] {
270
647
  return [
271
648
  {
272
649
  relativePath: ".maina/constitution.md",
273
- content: buildConstitution(stack),
650
+ content: constitutionOverride ?? buildConstitution(stack),
274
651
  },
275
652
  {
276
653
  relativePath: ".maina/prompts/review.md",
@@ -288,6 +665,26 @@ function getFileManifest(stack: DetectedStack): FileEntry[] {
288
665
  relativePath: ".github/workflows/maina-ci.yml",
289
666
  content: buildCiWorkflow(stack),
290
667
  },
668
+ {
669
+ relativePath: ".github/copilot-instructions.md",
670
+ content: buildCopilotInstructions(stack),
671
+ },
672
+ {
673
+ relativePath: ".mcp.json",
674
+ content: buildMcpJson(),
675
+ },
676
+ {
677
+ relativePath: "CLAUDE.md",
678
+ content: buildClaudeMd(stack),
679
+ },
680
+ {
681
+ relativePath: "GEMINI.md",
682
+ content: buildGeminiMd(stack),
683
+ },
684
+ {
685
+ relativePath: ".cursorrules",
686
+ content: buildCursorRules(stack),
687
+ },
291
688
  ];
292
689
  }
293
690
 
@@ -341,6 +738,7 @@ export async function bootstrap(
341
738
  options?: InitOptions,
342
739
  ): Promise<Result<InitReport>> {
343
740
  const force = options?.force ?? false;
741
+ const aiGenerate = options?.aiGenerate ?? false;
344
742
  const mainaDir = join(repoRoot, ".maina");
345
743
  const created: string[] = [];
346
744
  const skipped: string[] = [];
@@ -349,8 +747,8 @@ export async function bootstrap(
349
747
  // Detect project stack from package.json
350
748
  const detectedStack = detectStack(repoRoot);
351
749
 
352
- // Detect available verification tools on PATH
353
- const detectedToolsList = await detectTools();
750
+ // Detect available verification tools on PATH (filtered by project languages)
751
+ const detectedToolsList = await detectTools(detectedStack.languages);
354
752
 
355
753
  // Ensure .maina/ exists
356
754
  mkdirSync(mainaDir, { recursive: true });
@@ -360,8 +758,22 @@ export async function bootstrap(
360
758
  mkdirSync(join(repoRoot, dir), { recursive: true });
361
759
  }
362
760
 
761
+ // Try AI-generated constitution when requested
762
+ let constitutionOverride: string | undefined;
763
+ let aiGenerated = false;
764
+ if (aiGenerate) {
765
+ const aiConstitution = await tryGenerateConstitution(
766
+ repoRoot,
767
+ detectedStack,
768
+ );
769
+ if (aiConstitution) {
770
+ constitutionOverride = aiConstitution;
771
+ aiGenerated = true;
772
+ }
773
+ }
774
+
363
775
  // Scaffold each file with stack-aware templates
364
- const manifest = getFileManifest(detectedStack);
776
+ const manifest = getFileManifest(detectedStack, constitutionOverride);
365
777
  for (const entry of manifest) {
366
778
  const fullPath = join(repoRoot, entry.relativePath);
367
779
  const dirPath = join(fullPath, "..");
@@ -396,6 +808,7 @@ export async function bootstrap(
396
808
  directory: mainaDir,
397
809
  detectedStack,
398
810
  detectedTools: detectedToolsList,
811
+ aiGenerated,
399
812
  },
400
813
  };
401
814
  } catch (e) {