@mainahq/core 1.0.3 → 1.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/package.json +1 -1
- package/src/ai/__tests__/delegation.test.ts +55 -1
- package/src/ai/delegation.ts +5 -3
- package/src/context/__tests__/budget.test.ts +29 -6
- package/src/context/__tests__/engine.test.ts +1 -0
- package/src/context/__tests__/selector.test.ts +23 -3
- package/src/context/__tests__/wiki.test.ts +349 -0
- package/src/context/budget.ts +12 -8
- package/src/context/engine.ts +37 -0
- package/src/context/selector.ts +30 -4
- package/src/context/wiki.ts +296 -0
- package/src/db/index.ts +12 -0
- package/src/feedback/__tests__/capture.test.ts +166 -0
- package/src/feedback/__tests__/signals.test.ts +144 -0
- package/src/feedback/__tests__/tmp-capture-1775575256633-lah0etnzlj/feedback.db +0 -0
- package/src/feedback/__tests__/tmp-capture-1775575256640-2xmjme4qraa/feedback.db +0 -0
- package/src/feedback/capture.ts +102 -0
- package/src/feedback/signals.ts +68 -0
- package/src/index.ts +104 -0
- package/src/init/__tests__/init.test.ts +400 -3
- package/src/init/index.ts +368 -12
- package/src/language/__tests__/__fixtures__/detect/composer.lock +1 -0
- package/src/prompts/defaults/index.ts +3 -1
- package/src/prompts/defaults/wiki-compile.md +20 -0
- package/src/prompts/defaults/wiki-query.md +18 -0
- package/src/stats/__tests__/tool-usage.test.ts +133 -0
- package/src/stats/tracker.ts +92 -0
- package/src/verify/__tests__/pipeline.test.ts +11 -8
- package/src/verify/pipeline.ts +13 -1
- package/src/verify/tools/__tests__/wiki-lint.test.ts +784 -0
- package/src/verify/tools/wiki-lint-runner.ts +38 -0
- package/src/verify/tools/wiki-lint.ts +898 -0
- package/src/wiki/__tests__/compiler.test.ts +389 -0
- package/src/wiki/__tests__/extractors/code.test.ts +99 -0
- package/src/wiki/__tests__/extractors/decision.test.ts +323 -0
- package/src/wiki/__tests__/extractors/feature.test.ts +186 -0
- package/src/wiki/__tests__/extractors/workflow.test.ts +131 -0
- package/src/wiki/__tests__/graph.test.ts +344 -0
- package/src/wiki/__tests__/hooks.test.ts +119 -0
- package/src/wiki/__tests__/indexer.test.ts +285 -0
- package/src/wiki/__tests__/linker.test.ts +230 -0
- package/src/wiki/__tests__/louvain.test.ts +229 -0
- package/src/wiki/__tests__/query.test.ts +316 -0
- package/src/wiki/__tests__/schema.test.ts +114 -0
- package/src/wiki/__tests__/signals.test.ts +474 -0
- package/src/wiki/__tests__/state.test.ts +168 -0
- package/src/wiki/__tests__/tracking.test.ts +118 -0
- package/src/wiki/__tests__/types.test.ts +387 -0
- package/src/wiki/compiler.ts +1075 -0
- package/src/wiki/extractors/code.ts +90 -0
- package/src/wiki/extractors/decision.ts +217 -0
- package/src/wiki/extractors/feature.ts +206 -0
- package/src/wiki/extractors/workflow.ts +112 -0
- package/src/wiki/graph.ts +445 -0
- package/src/wiki/hooks.ts +49 -0
- package/src/wiki/indexer.ts +105 -0
- package/src/wiki/linker.ts +117 -0
- package/src/wiki/louvain.ts +190 -0
- package/src/wiki/prompts/compile-architecture.md +59 -0
- package/src/wiki/prompts/compile-decision.md +66 -0
- package/src/wiki/prompts/compile-entity.md +56 -0
- package/src/wiki/prompts/compile-feature.md +60 -0
- package/src/wiki/prompts/compile-module.md +42 -0
- package/src/wiki/prompts/wiki-query.md +25 -0
- package/src/wiki/query.ts +338 -0
- package/src/wiki/schema.ts +111 -0
- package/src/wiki/signals.ts +368 -0
- package/src/wiki/state.ts +89 -0
- package/src/wiki/tracking.ts +30 -0
- package/src/wiki/types.ts +169 -0
- package/src/workflow/context.ts +26 -0
|
@@ -274,7 +274,12 @@ describe("bootstrap", () => {
|
|
|
274
274
|
|
|
275
275
|
// ── .mcp.json generation ──────────────────────────────────────────────
|
|
276
276
|
|
|
277
|
-
test("creates .mcp.json at repo root", async () => {
|
|
277
|
+
test("creates .mcp.json at repo root with npx for node runtime", async () => {
|
|
278
|
+
writeFileSync(
|
|
279
|
+
join(tmpDir, "package.json"),
|
|
280
|
+
JSON.stringify({ dependencies: { express: "^4" } }),
|
|
281
|
+
);
|
|
282
|
+
|
|
278
283
|
const result = await bootstrap(tmpDir);
|
|
279
284
|
expect(result.ok).toBe(true);
|
|
280
285
|
|
|
@@ -284,8 +289,25 @@ describe("bootstrap", () => {
|
|
|
284
289
|
const content = JSON.parse(readFileSync(mcpPath, "utf-8"));
|
|
285
290
|
expect(content.mcpServers).toBeDefined();
|
|
286
291
|
expect(content.mcpServers.maina).toBeDefined();
|
|
287
|
-
expect(content.mcpServers.maina.command).toBe("
|
|
288
|
-
expect(content.mcpServers.maina.args).toEqual(["--mcp"]);
|
|
292
|
+
expect(content.mcpServers.maina.command).toBe("npx");
|
|
293
|
+
expect(content.mcpServers.maina.args).toEqual(["@mainahq/cli", "--mcp"]);
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
test("creates .mcp.json with bunx for bun runtime", async () => {
|
|
297
|
+
writeFileSync(
|
|
298
|
+
join(tmpDir, "package.json"),
|
|
299
|
+
JSON.stringify({ devDependencies: { "@types/bun": "latest" } }),
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
const result = await bootstrap(tmpDir);
|
|
303
|
+
expect(result.ok).toBe(true);
|
|
304
|
+
|
|
305
|
+
const mcpPath = join(tmpDir, ".mcp.json");
|
|
306
|
+
expect(existsSync(mcpPath)).toBe(true);
|
|
307
|
+
|
|
308
|
+
const content = JSON.parse(readFileSync(mcpPath, "utf-8"));
|
|
309
|
+
expect(content.mcpServers.maina.command).toBe("bunx");
|
|
310
|
+
expect(content.mcpServers.maina.args).toEqual(["@mainahq/cli", "--mcp"]);
|
|
289
311
|
});
|
|
290
312
|
|
|
291
313
|
test("does not overwrite existing .mcp.json", async () => {
|
|
@@ -513,13 +535,388 @@ describe("bootstrap", () => {
|
|
|
513
535
|
".github/workflows/maina-ci.yml",
|
|
514
536
|
".github/copilot-instructions.md",
|
|
515
537
|
".mcp.json",
|
|
538
|
+
".claude/settings.json",
|
|
516
539
|
"CLAUDE.md",
|
|
517
540
|
"GEMINI.md",
|
|
518
541
|
".cursorrules",
|
|
542
|
+
".windsurfrules",
|
|
543
|
+
".clinerules",
|
|
544
|
+
".continue/config.yaml",
|
|
545
|
+
".continue/mcpServers/maina.json",
|
|
546
|
+
".roo/mcp.json",
|
|
547
|
+
".roo/rules/maina.md",
|
|
548
|
+
".amazonq/mcp.json",
|
|
549
|
+
".aider.conf.yml",
|
|
550
|
+
"CONVENTIONS.md",
|
|
519
551
|
];
|
|
520
552
|
for (const f of expectedFiles) {
|
|
521
553
|
expect(result.value.created).toContain(f);
|
|
522
554
|
}
|
|
523
555
|
}
|
|
524
556
|
});
|
|
557
|
+
|
|
558
|
+
// ── .claude/settings.json generation ──────────────────────────────────
|
|
559
|
+
|
|
560
|
+
test("creates .claude/settings.json for Claude Code MCP config", async () => {
|
|
561
|
+
writeFileSync(
|
|
562
|
+
join(tmpDir, "package.json"),
|
|
563
|
+
JSON.stringify({ devDependencies: { "@types/bun": "latest" } }),
|
|
564
|
+
);
|
|
565
|
+
|
|
566
|
+
const result = await bootstrap(tmpDir);
|
|
567
|
+
expect(result.ok).toBe(true);
|
|
568
|
+
|
|
569
|
+
const settingsPath = join(tmpDir, ".claude", "settings.json");
|
|
570
|
+
expect(existsSync(settingsPath)).toBe(true);
|
|
571
|
+
|
|
572
|
+
const content = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
|
573
|
+
expect(content.mcpServers).toBeDefined();
|
|
574
|
+
expect(content.mcpServers.maina).toBeDefined();
|
|
575
|
+
expect(content.mcpServers.maina.command).toBe("bunx");
|
|
576
|
+
expect(content.mcpServers.maina.args).toEqual(["@mainahq/cli", "--mcp"]);
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
test(".claude/settings.json uses npx for node runtime", async () => {
|
|
580
|
+
writeFileSync(
|
|
581
|
+
join(tmpDir, "package.json"),
|
|
582
|
+
JSON.stringify({ dependencies: { express: "^4" } }),
|
|
583
|
+
);
|
|
584
|
+
|
|
585
|
+
const result = await bootstrap(tmpDir);
|
|
586
|
+
expect(result.ok).toBe(true);
|
|
587
|
+
|
|
588
|
+
const settingsPath = join(tmpDir, ".claude", "settings.json");
|
|
589
|
+
expect(existsSync(settingsPath)).toBe(true);
|
|
590
|
+
|
|
591
|
+
const content = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
|
592
|
+
expect(content.mcpServers.maina.command).toBe("npx");
|
|
593
|
+
expect(content.mcpServers.maina.args).toEqual(["@mainahq/cli", "--mcp"]);
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
// ── CLAUDE.md includes wiki commands ─────────────────────────────────
|
|
597
|
+
|
|
598
|
+
test("CLAUDE.md includes wiki commands", async () => {
|
|
599
|
+
const result = await bootstrap(tmpDir);
|
|
600
|
+
expect(result.ok).toBe(true);
|
|
601
|
+
|
|
602
|
+
const content = readFileSync(join(tmpDir, "CLAUDE.md"), "utf-8");
|
|
603
|
+
expect(content).toContain("maina wiki init");
|
|
604
|
+
expect(content).toContain("maina wiki query");
|
|
605
|
+
expect(content).toContain("maina wiki compile");
|
|
606
|
+
expect(content).toContain("maina wiki status");
|
|
607
|
+
expect(content).toContain("maina wiki lint");
|
|
608
|
+
expect(content).toContain("maina brainstorm");
|
|
609
|
+
expect(content).toContain("maina ticket");
|
|
610
|
+
expect(content).toContain("maina design");
|
|
611
|
+
expect(content).toContain("maina spec");
|
|
612
|
+
expect(content).toContain("maina slop");
|
|
613
|
+
expect(content).toContain("maina explain");
|
|
614
|
+
expect(content).toContain("maina status");
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
// ── MCP_TOOLS_TABLE includes wiki tools ──────────────────────────────
|
|
618
|
+
|
|
619
|
+
test("MCP tools table includes wikiQuery and wikiStatus", async () => {
|
|
620
|
+
const result = await bootstrap(tmpDir);
|
|
621
|
+
expect(result.ok).toBe(true);
|
|
622
|
+
|
|
623
|
+
// Check multiple agent files that embed the MCP tools table
|
|
624
|
+
const claudeContent = readFileSync(join(tmpDir, "CLAUDE.md"), "utf-8");
|
|
625
|
+
expect(claudeContent).toContain("wikiQuery");
|
|
626
|
+
expect(claudeContent).toContain("wikiStatus");
|
|
627
|
+
|
|
628
|
+
const agentsContent = readFileSync(join(tmpDir, "AGENTS.md"), "utf-8");
|
|
629
|
+
expect(agentsContent).toContain("wikiQuery");
|
|
630
|
+
expect(agentsContent).toContain("wikiStatus");
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
// ── Wiki section in agent files ──────────────────────────────────────
|
|
634
|
+
|
|
635
|
+
test("agent files include wiki section", async () => {
|
|
636
|
+
const result = await bootstrap(tmpDir);
|
|
637
|
+
expect(result.ok).toBe(true);
|
|
638
|
+
|
|
639
|
+
const claudeContent = readFileSync(join(tmpDir, "CLAUDE.md"), "utf-8");
|
|
640
|
+
expect(claudeContent).toContain("## Wiki");
|
|
641
|
+
expect(claudeContent).toContain("wikiQuery");
|
|
642
|
+
|
|
643
|
+
const agentsContent = readFileSync(join(tmpDir, "AGENTS.md"), "utf-8");
|
|
644
|
+
expect(agentsContent).toContain("## Wiki");
|
|
645
|
+
|
|
646
|
+
const geminiContent = readFileSync(join(tmpDir, "GEMINI.md"), "utf-8");
|
|
647
|
+
expect(geminiContent).toContain("## Wiki");
|
|
648
|
+
|
|
649
|
+
const cursorContent = readFileSync(join(tmpDir, ".cursorrules"), "utf-8");
|
|
650
|
+
expect(cursorContent).toContain("## Wiki");
|
|
651
|
+
|
|
652
|
+
const copilotContent = readFileSync(
|
|
653
|
+
join(tmpDir, ".github", "copilot-instructions.md"),
|
|
654
|
+
"utf-8",
|
|
655
|
+
);
|
|
656
|
+
expect(copilotContent).toContain("## Wiki");
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
// ── Windsurf ──────────────────────────────────────────────────────────
|
|
660
|
+
|
|
661
|
+
test("creates .windsurfrules at repo root", async () => {
|
|
662
|
+
const result = await bootstrap(tmpDir);
|
|
663
|
+
expect(result.ok).toBe(true);
|
|
664
|
+
|
|
665
|
+
const filePath = join(tmpDir, ".windsurfrules");
|
|
666
|
+
expect(existsSync(filePath)).toBe(true);
|
|
667
|
+
|
|
668
|
+
const content = readFileSync(filePath, "utf-8");
|
|
669
|
+
expect(content).toContain("Windsurf Rules");
|
|
670
|
+
expect(content).toContain("constitution.md");
|
|
671
|
+
expect(content).toContain("brainstorm");
|
|
672
|
+
expect(content).toContain("maina verify");
|
|
673
|
+
expect(content).toContain("getContext");
|
|
674
|
+
expect(content).toContain("checkSlop");
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
test(".windsurfrules includes MCP tools table", async () => {
|
|
678
|
+
const result = await bootstrap(tmpDir);
|
|
679
|
+
expect(result.ok).toBe(true);
|
|
680
|
+
|
|
681
|
+
const content = readFileSync(join(tmpDir, ".windsurfrules"), "utf-8");
|
|
682
|
+
expect(content).toContain("MCP Tools");
|
|
683
|
+
expect(content).toContain("getContext");
|
|
684
|
+
expect(content).toContain("reviewCode");
|
|
685
|
+
expect(content).toContain("wikiQuery");
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
test("merges maina section into existing .windsurfrules", async () => {
|
|
689
|
+
writeFileSync(join(tmpDir, ".windsurfrules"), "# My Windsurf Rules\n");
|
|
690
|
+
|
|
691
|
+
const result = await bootstrap(tmpDir);
|
|
692
|
+
expect(result.ok).toBe(true);
|
|
693
|
+
if (result.ok) {
|
|
694
|
+
expect(result.value.updated).toContain(".windsurfrules");
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
const content = readFileSync(join(tmpDir, ".windsurfrules"), "utf-8");
|
|
698
|
+
expect(content).toContain("# My Windsurf Rules");
|
|
699
|
+
expect(content).toContain("## Maina");
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
// ── Cline ─────────────────────────────────────────────────────────────
|
|
703
|
+
|
|
704
|
+
test("creates .clinerules at repo root", async () => {
|
|
705
|
+
const result = await bootstrap(tmpDir);
|
|
706
|
+
expect(result.ok).toBe(true);
|
|
707
|
+
|
|
708
|
+
const filePath = join(tmpDir, ".clinerules");
|
|
709
|
+
expect(existsSync(filePath)).toBe(true);
|
|
710
|
+
|
|
711
|
+
const content = readFileSync(filePath, "utf-8");
|
|
712
|
+
expect(content).toContain("Cline Rules");
|
|
713
|
+
expect(content).toContain("constitution.md");
|
|
714
|
+
expect(content).toContain("brainstorm");
|
|
715
|
+
expect(content).toContain("maina verify");
|
|
716
|
+
expect(content).toContain("getContext");
|
|
717
|
+
expect(content).toContain("checkSlop");
|
|
718
|
+
});
|
|
719
|
+
|
|
720
|
+
test("merges maina section into existing .clinerules", async () => {
|
|
721
|
+
writeFileSync(join(tmpDir, ".clinerules"), "# My Cline Rules\n");
|
|
722
|
+
|
|
723
|
+
const result = await bootstrap(tmpDir);
|
|
724
|
+
expect(result.ok).toBe(true);
|
|
725
|
+
if (result.ok) {
|
|
726
|
+
expect(result.value.updated).toContain(".clinerules");
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
const content = readFileSync(join(tmpDir, ".clinerules"), "utf-8");
|
|
730
|
+
expect(content).toContain("# My Cline Rules");
|
|
731
|
+
expect(content).toContain("## Maina");
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
// ── Continue.dev ──────────────────────────────────────────────────────
|
|
735
|
+
|
|
736
|
+
test("creates .continue/mcpServers/maina.json", async () => {
|
|
737
|
+
const result = await bootstrap(tmpDir);
|
|
738
|
+
expect(result.ok).toBe(true);
|
|
739
|
+
|
|
740
|
+
const mcpPath = join(tmpDir, ".continue", "mcpServers", "maina.json");
|
|
741
|
+
expect(existsSync(mcpPath)).toBe(true);
|
|
742
|
+
|
|
743
|
+
const content = JSON.parse(readFileSync(mcpPath, "utf-8"));
|
|
744
|
+
expect(content.maina).toBeDefined();
|
|
745
|
+
expect(content.maina.command).toBe("npx");
|
|
746
|
+
expect(content.maina.args).toEqual(["@mainahq/cli", "--mcp"]);
|
|
747
|
+
});
|
|
748
|
+
|
|
749
|
+
test("creates .continue/config.yaml", async () => {
|
|
750
|
+
const result = await bootstrap(tmpDir);
|
|
751
|
+
expect(result.ok).toBe(true);
|
|
752
|
+
|
|
753
|
+
const configPath = join(tmpDir, ".continue", "config.yaml");
|
|
754
|
+
expect(existsSync(configPath)).toBe(true);
|
|
755
|
+
|
|
756
|
+
const content = readFileSync(configPath, "utf-8");
|
|
757
|
+
expect(content).toContain("customInstructions");
|
|
758
|
+
expect(content).toContain("Maina");
|
|
759
|
+
expect(content).toContain("constitution.md");
|
|
760
|
+
});
|
|
761
|
+
|
|
762
|
+
test(".continue/mcpServers/maina.json uses bunx for bun runtime", async () => {
|
|
763
|
+
writeFileSync(
|
|
764
|
+
join(tmpDir, "package.json"),
|
|
765
|
+
JSON.stringify({ devDependencies: { "@types/bun": "latest" } }),
|
|
766
|
+
);
|
|
767
|
+
|
|
768
|
+
const result = await bootstrap(tmpDir);
|
|
769
|
+
expect(result.ok).toBe(true);
|
|
770
|
+
|
|
771
|
+
const mcpPath = join(tmpDir, ".continue", "mcpServers", "maina.json");
|
|
772
|
+
const content = JSON.parse(readFileSync(mcpPath, "utf-8"));
|
|
773
|
+
expect(content.maina.command).toBe("bunx");
|
|
774
|
+
});
|
|
775
|
+
|
|
776
|
+
// ── Roo Code ──────────────────────────────────────────────────────────
|
|
777
|
+
|
|
778
|
+
test("creates .roo/mcp.json", async () => {
|
|
779
|
+
const result = await bootstrap(tmpDir);
|
|
780
|
+
expect(result.ok).toBe(true);
|
|
781
|
+
|
|
782
|
+
const mcpPath = join(tmpDir, ".roo", "mcp.json");
|
|
783
|
+
expect(existsSync(mcpPath)).toBe(true);
|
|
784
|
+
|
|
785
|
+
const content = JSON.parse(readFileSync(mcpPath, "utf-8"));
|
|
786
|
+
expect(content.mcpServers).toBeDefined();
|
|
787
|
+
expect(content.mcpServers.maina).toBeDefined();
|
|
788
|
+
expect(content.mcpServers.maina.command).toBe("npx");
|
|
789
|
+
expect(content.mcpServers.maina.args).toEqual(["@mainahq/cli", "--mcp"]);
|
|
790
|
+
});
|
|
791
|
+
|
|
792
|
+
test("creates .roo/rules/maina.md with MCP tools", async () => {
|
|
793
|
+
const result = await bootstrap(tmpDir);
|
|
794
|
+
expect(result.ok).toBe(true);
|
|
795
|
+
|
|
796
|
+
const rulesPath = join(tmpDir, ".roo", "rules", "maina.md");
|
|
797
|
+
expect(existsSync(rulesPath)).toBe(true);
|
|
798
|
+
|
|
799
|
+
const content = readFileSync(rulesPath, "utf-8");
|
|
800
|
+
expect(content).toContain("# Maina");
|
|
801
|
+
expect(content).toContain("constitution.md");
|
|
802
|
+
expect(content).toContain("getContext");
|
|
803
|
+
expect(content).toContain("checkSlop");
|
|
804
|
+
expect(content).toContain("reviewCode");
|
|
805
|
+
expect(content).toContain("wikiQuery");
|
|
806
|
+
});
|
|
807
|
+
|
|
808
|
+
test(".roo/mcp.json uses bunx for bun runtime", async () => {
|
|
809
|
+
writeFileSync(
|
|
810
|
+
join(tmpDir, "package.json"),
|
|
811
|
+
JSON.stringify({ devDependencies: { "@types/bun": "latest" } }),
|
|
812
|
+
);
|
|
813
|
+
|
|
814
|
+
const result = await bootstrap(tmpDir);
|
|
815
|
+
expect(result.ok).toBe(true);
|
|
816
|
+
|
|
817
|
+
const mcpPath = join(tmpDir, ".roo", "mcp.json");
|
|
818
|
+
const content = JSON.parse(readFileSync(mcpPath, "utf-8"));
|
|
819
|
+
expect(content.mcpServers.maina.command).toBe("bunx");
|
|
820
|
+
});
|
|
821
|
+
|
|
822
|
+
// ── Amazon Q ──────────────────────────────────────────────────────────
|
|
823
|
+
|
|
824
|
+
test("creates .amazonq/mcp.json", async () => {
|
|
825
|
+
const result = await bootstrap(tmpDir);
|
|
826
|
+
expect(result.ok).toBe(true);
|
|
827
|
+
|
|
828
|
+
const mcpPath = join(tmpDir, ".amazonq", "mcp.json");
|
|
829
|
+
expect(existsSync(mcpPath)).toBe(true);
|
|
830
|
+
|
|
831
|
+
const content = JSON.parse(readFileSync(mcpPath, "utf-8"));
|
|
832
|
+
expect(content.mcpServers).toBeDefined();
|
|
833
|
+
expect(content.mcpServers.maina).toBeDefined();
|
|
834
|
+
expect(content.mcpServers.maina.command).toBe("npx");
|
|
835
|
+
expect(content.mcpServers.maina.args).toEqual(["@mainahq/cli", "--mcp"]);
|
|
836
|
+
});
|
|
837
|
+
|
|
838
|
+
test(".amazonq/mcp.json uses bunx for bun runtime", async () => {
|
|
839
|
+
writeFileSync(
|
|
840
|
+
join(tmpDir, "package.json"),
|
|
841
|
+
JSON.stringify({ devDependencies: { "@types/bun": "latest" } }),
|
|
842
|
+
);
|
|
843
|
+
|
|
844
|
+
const result = await bootstrap(tmpDir);
|
|
845
|
+
expect(result.ok).toBe(true);
|
|
846
|
+
|
|
847
|
+
const mcpPath = join(tmpDir, ".amazonq", "mcp.json");
|
|
848
|
+
const content = JSON.parse(readFileSync(mcpPath, "utf-8"));
|
|
849
|
+
expect(content.mcpServers.maina.command).toBe("bunx");
|
|
850
|
+
});
|
|
851
|
+
|
|
852
|
+
// ── Aider ─────────────────────────────────────────────────────────────
|
|
853
|
+
|
|
854
|
+
test("creates .aider.conf.yml", async () => {
|
|
855
|
+
const result = await bootstrap(tmpDir);
|
|
856
|
+
expect(result.ok).toBe(true);
|
|
857
|
+
|
|
858
|
+
const configPath = join(tmpDir, ".aider.conf.yml");
|
|
859
|
+
expect(existsSync(configPath)).toBe(true);
|
|
860
|
+
|
|
861
|
+
const content = readFileSync(configPath, "utf-8");
|
|
862
|
+
expect(content).toContain("CONVENTIONS.md");
|
|
863
|
+
expect(content).toContain("constitution.md");
|
|
864
|
+
expect(content).toContain("auto-commits: false");
|
|
865
|
+
});
|
|
866
|
+
|
|
867
|
+
test("creates CONVENTIONS.md", async () => {
|
|
868
|
+
const result = await bootstrap(tmpDir);
|
|
869
|
+
expect(result.ok).toBe(true);
|
|
870
|
+
|
|
871
|
+
const convPath = join(tmpDir, "CONVENTIONS.md");
|
|
872
|
+
expect(existsSync(convPath)).toBe(true);
|
|
873
|
+
|
|
874
|
+
const content = readFileSync(convPath, "utf-8");
|
|
875
|
+
expect(content).toContain("# Conventions");
|
|
876
|
+
expect(content).toContain("constitution.md");
|
|
877
|
+
expect(content).toContain("brainstorm");
|
|
878
|
+
expect(content).toContain("getContext");
|
|
879
|
+
expect(content).toContain("checkSlop");
|
|
880
|
+
expect(content).toContain("reviewCode");
|
|
881
|
+
expect(content).toContain("maina verify");
|
|
882
|
+
expect(content).toContain("wikiQuery");
|
|
883
|
+
});
|
|
884
|
+
|
|
885
|
+
test("merges maina section into existing CONVENTIONS.md", async () => {
|
|
886
|
+
writeFileSync(join(tmpDir, "CONVENTIONS.md"), "# My Conventions\n");
|
|
887
|
+
|
|
888
|
+
const result = await bootstrap(tmpDir);
|
|
889
|
+
expect(result.ok).toBe(true);
|
|
890
|
+
if (result.ok) {
|
|
891
|
+
expect(result.value.updated).toContain("CONVENTIONS.md");
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
const content = readFileSync(join(tmpDir, "CONVENTIONS.md"), "utf-8");
|
|
895
|
+
expect(content).toContain("# My Conventions");
|
|
896
|
+
expect(content).toContain("## Maina");
|
|
897
|
+
});
|
|
898
|
+
|
|
899
|
+
// ── All rules files consistent ───────────────────────────────────────
|
|
900
|
+
|
|
901
|
+
test("all rules files contain MCP tools table", async () => {
|
|
902
|
+
const result = await bootstrap(tmpDir);
|
|
903
|
+
expect(result.ok).toBe(true);
|
|
904
|
+
|
|
905
|
+
const rulesFiles = [
|
|
906
|
+
".cursorrules",
|
|
907
|
+
".windsurfrules",
|
|
908
|
+
".clinerules",
|
|
909
|
+
".roo/rules/maina.md",
|
|
910
|
+
"CONVENTIONS.md",
|
|
911
|
+
];
|
|
912
|
+
|
|
913
|
+
for (const f of rulesFiles) {
|
|
914
|
+
const content = readFileSync(join(tmpDir, f), "utf-8");
|
|
915
|
+
expect(content).toContain("getContext");
|
|
916
|
+
expect(content).toContain("checkSlop");
|
|
917
|
+
expect(content).toContain("reviewCode");
|
|
918
|
+
expect(content).toContain("suggestTests");
|
|
919
|
+
expect(content).toContain("wikiQuery");
|
|
920
|
+
}
|
|
921
|
+
});
|
|
525
922
|
});
|