@mainahq/core 1.0.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 {
@@ -179,6 +267,11 @@ function buildAgentsMd(stack: DetectedStack): string {
179
267
 
180
268
  This repo uses [Maina](https://github.com/mainahq/maina) for verification-first development.
181
269
 
270
+ ## Workflow Order
271
+
272
+ Follow this order for every feature:
273
+ \`${WORKFLOW_ORDER}\`
274
+
182
275
  ## Quick Start
183
276
  \`\`\`bash
184
277
  ${installCmd}
@@ -199,6 +292,10 @@ 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
  |------|---------|-----------|
@@ -206,6 +303,9 @@ maina commit # verify + commit
206
303
  | \`AGENTS.md\` | Agent instructions — commands, conventions | Team |
207
304
  | \`.github/copilot-instructions.md\` | Copilot agent instructions + MCP tools | Team |
208
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 |
209
309
  | \`.maina/prompts/*.md\` | Prompt overrides for review/commit/etc | Maina (via \`maina learn\`) |
210
310
 
211
311
  ## Runtime
@@ -220,7 +320,12 @@ function buildCopilotInstructions(stack: DetectedStack): string {
220
320
 
221
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.
222
322
 
223
- ## Workflow
323
+ ## Workflow Order
324
+
325
+ Follow this order for every feature:
326
+ \`${WORKFLOW_ORDER}\`
327
+
328
+ ## Step-by-step
224
329
 
225
330
  1. **Get context** — call \`maina getContext\` to understand codebase state
226
331
  2. **Write tests first** — TDD always. Write failing tests, then implement
@@ -230,14 +335,7 @@ You are working on a codebase verified by [Maina](https://mainahq.com), the veri
230
335
 
231
336
  ## Available MCP Tools
232
337
 
233
- | Tool | When to use |
234
- |------|-------------|
235
- | \`getContext\` | Before starting — understand branch state and verification status |
236
- | \`verify\` | After changes — run the full verification pipeline |
237
- | \`checkSlop\` | On changed files — detect AI-generated slop patterns |
238
- | \`reviewCode\` | On your diff — two-stage review (spec compliance + code quality) |
239
- | \`suggestTests\` | When implementing — generate TDD test stubs |
240
- | \`getConventions\` | Understand project coding conventions |
338
+ ${MCP_TOOLS_TABLE}
241
339
 
242
340
  ## Conventions
243
341
 
@@ -253,6 +351,242 @@ Issues labeled \`audit\` come from maina's daily verification. Fix the specific
253
351
  `;
254
352
  }
255
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
+
256
590
  const REVIEW_PROMPT_TEMPLATE = `# Review Prompt
257
591
 
258
592
  Review the following code changes for:
@@ -306,11 +640,14 @@ interface FileEntry {
306
640
  content: string;
307
641
  }
308
642
 
309
- function getFileManifest(stack: DetectedStack): FileEntry[] {
643
+ function getFileManifest(
644
+ stack: DetectedStack,
645
+ constitutionOverride?: string,
646
+ ): FileEntry[] {
310
647
  return [
311
648
  {
312
649
  relativePath: ".maina/constitution.md",
313
- content: buildConstitution(stack),
650
+ content: constitutionOverride ?? buildConstitution(stack),
314
651
  },
315
652
  {
316
653
  relativePath: ".maina/prompts/review.md",
@@ -332,6 +669,22 @@ function getFileManifest(stack: DetectedStack): FileEntry[] {
332
669
  relativePath: ".github/copilot-instructions.md",
333
670
  content: buildCopilotInstructions(stack),
334
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
+ },
335
688
  ];
336
689
  }
337
690
 
@@ -385,6 +738,7 @@ export async function bootstrap(
385
738
  options?: InitOptions,
386
739
  ): Promise<Result<InitReport>> {
387
740
  const force = options?.force ?? false;
741
+ const aiGenerate = options?.aiGenerate ?? false;
388
742
  const mainaDir = join(repoRoot, ".maina");
389
743
  const created: string[] = [];
390
744
  const skipped: string[] = [];
@@ -393,8 +747,8 @@ export async function bootstrap(
393
747
  // Detect project stack from package.json
394
748
  const detectedStack = detectStack(repoRoot);
395
749
 
396
- // Detect available verification tools on PATH
397
- const detectedToolsList = await detectTools();
750
+ // Detect available verification tools on PATH (filtered by project languages)
751
+ const detectedToolsList = await detectTools(detectedStack.languages);
398
752
 
399
753
  // Ensure .maina/ exists
400
754
  mkdirSync(mainaDir, { recursive: true });
@@ -404,8 +758,22 @@ export async function bootstrap(
404
758
  mkdirSync(join(repoRoot, dir), { recursive: true });
405
759
  }
406
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
+
407
775
  // Scaffold each file with stack-aware templates
408
- const manifest = getFileManifest(detectedStack);
776
+ const manifest = getFileManifest(detectedStack, constitutionOverride);
409
777
  for (const entry of manifest) {
410
778
  const fullPath = join(repoRoot, entry.relativePath);
411
779
  const dirPath = join(fullPath, "..");
@@ -440,6 +808,7 @@ export async function bootstrap(
440
808
  directory: mainaDir,
441
809
  detectedStack,
442
810
  detectedTools: detectedToolsList,
811
+ aiGenerated,
443
812
  },
444
813
  };
445
814
  } catch (e) {